Add an option to commit patches after they have been applied

This commit is contained in:
Aleksei Kuzmin 2018-08-13 17:54:07 -07:00
Родитель f3e36be426
Коммит ea761327d7
4 изменённых файлов: 122 добавлений и 23 удалений

Просмотреть файл

@ -20,7 +20,7 @@ def main():
args = parse_args()
for folder in args.folders:
error = apply_patches_for_dir(folder)
error = apply_patches_for_dir(folder, args.commit)
if error:
sys.stderr.write(error + '\n')
sys.stderr.flush()
@ -29,14 +29,14 @@ def main():
return 0
def apply_patches_for_dir(directory):
def apply_patches_for_dir(directory, commit):
for root, dirs, files in os.walk(directory):
config = PatchesConfig.from_directory(root)
patches_list = config.get_patches_list()
if patches_list is None:
continue
(success, failed_patches) = patches_list.apply()
(success, failed_patches) = patches_list.apply(commit=commit)
if not success:
patch_path = failed_patches[0].get_file_path()
return '{0} failed to apply'.format(os.path.basename(patch_path))
@ -45,6 +45,8 @@ def apply_patches_for_dir(directory):
def parse_args():
parser = argparse.ArgumentParser(description='Apply all required patches.')
parser.add_argument('--commit', default=False, action='store_true',
help='Commit a patch after it has been applied')
parser.add_argument('-t', '--target_arch',
help='Target architecture')

Просмотреть файл

@ -4,26 +4,60 @@ Everything in here should be project agnostic, shouldn't rely on project's struc
and make any assumptions about the passed arguments or calls outcomes.
"""
import os
import subprocess
from util import scoped_cwd
def apply(repo, patch_path, reverse=False):
def is_repo_root(path):
path_exists = os.path.exists(path)
if not path_exists:
return False
git_folder_path = os.path.join(path, '.git')
git_folder_exists = os.path.exists(git_folder_path)
return git_folder_exists
def get_repo_root(path):
"""Finds a closest ancestor folder which is a repo root."""
norm_path = os.path.normpath(path)
norm_path_exists = os.path.exists(norm_path)
if not norm_path_exists:
return None
if is_repo_root(norm_path):
return norm_path
parent_path = os.path.dirname(norm_path)
# Check if we're in the root folder already.
if parent_path == norm_path:
return None
return get_repo_root(parent_path)
def apply(repo, patch_path, directory=None, index=False, reverse=False):
args = ['git', 'apply',
'--directory', repo,
'--ignore-space-change',
'--ignore-whitespace',
'--whitespace', 'fix'
]
if directory:
args += ['--directory', directory]
if index:
args += ['--index']
if reverse:
args += ['--reverse']
args += ['--', patch_path]
return_code = subprocess.call(args)
applied_successfully = (return_code == 0)
return applied_successfully
with scoped_cwd(repo):
return_code = subprocess.call(args)
applied_successfully = (return_code == 0)
return applied_successfully
def get_patch(repo, commit_hash):
@ -42,3 +76,23 @@ def get_head_commit(repo):
with scoped_cwd(repo):
return subprocess.check_output(args).strip()
def commit(repo, author, message):
""" Commit whatever in the index is now."""
# Let's setup committer info so git won't complain about it being missing.
# TODO: Is there a better way to set committer's name and email?
env = os.environ.copy()
env['GIT_COMMITTER_NAME'] = 'Anonymous Committer'
env['GIT_COMMITTER_EMAIL'] = 'anonymous@electronjs.org'
args = ['git', 'commit',
'--author', author,
'--message', message
]
with scoped_cwd(repo):
return_code = subprocess.call(args, env=env)
committed_successfully = (return_code == 0)
return committed_successfully

Просмотреть файл

@ -1,22 +1,46 @@
import os
import sys
import git
from config import VENDOR_DIR
PYYAML_LIB_DIR = os.path.join(VENDOR_DIR, 'pyyaml', 'lib')
sys.path.append(PYYAML_LIB_DIR)
import yaml
from git import apply as git_apply
class Patch:
def __init__(self, file_path, repo_path):
def __init__(self, file_path, repo_path, paths_prefix=None, author='Anonymous <anonymous@electronjs.org>', description=None):
self.author = author
self.description = description
self.file_path = file_path
self.paths_prefix = paths_prefix
self.repo_path = repo_path
def apply(self, reverse=False):
return git_apply(self.repo_path, self.file_path, reverse=reverse)
def apply(self, reverse=False, commit=False):
# Add the change to index only if we're going to commit it later.
patch_applied = git.apply(self.repo_path, self.file_path, directory=self.paths_prefix, index=commit, reverse=reverse)
if not patch_applied:
return False
if commit:
message = self.__get_commit_message(reverse)
patch_committed = git.commit(self.repo_path, author=self.author, message=message)
return patch_committed
return True
def __get_commit_message(self, reverse):
message = self.description
if message is None:
message = os.path.basename(self.file_path)
if reverse:
message = 'Revert: ' + message
return message
def reverse(self):
return self.apply(reverse=True)
@ -32,12 +56,12 @@ class PatchesList:
def __len__(self):
return len(self.patches)
def apply(self, reverse=False, stop_on_error=True):
def apply(self, reverse=False, stop_on_error=True, commit=False):
all_patches_applied = True
failed_patches = []
for patch in self.patches:
applied_successfully = patch.apply(reverse=reverse)
applied_successfully = patch.apply(reverse=reverse, commit=commit)
if not applied_successfully:
all_patches_applied = False
@ -74,25 +98,44 @@ class PatchesConfig:
return contents
def __create_patch(self, raw_data, base_directory, repo_path):
def __create_patch(self, raw_data, base_directory, repo_path, paths_prefix):
author = raw_data['author']
if author is None: # Shouldn't actually happen.
author = 'Anonymous <anonymous@electronjs.org>'
relative_file_path = raw_data['file']
absolute_file_path = os.path.join(base_directory, relative_file_path)
return Patch(absolute_file_path, repo_path)
# Use a patch file path as a commit summary
# and optional description as a commit body.
description = relative_file_path
if raw_data['description'] is not None:
description += '\n\n' + raw_data['description']
return Patch(absolute_file_path, repo_path, paths_prefix=paths_prefix, author=author, description=description)
def get_patches_list(self):
config_contents = self.__parse()
if config_contents is None:
return None
repo_path = config_contents['repo']
if sys.platform == 'win32':
repo_path = repo_path.replace('/', '\\')
project_root = git.get_repo_root(self.path)
assert(project_root)
relative_repo_path = os.path.normpath(config_contents['repo'])
absolute_repo_path = os.path.join(project_root, relative_repo_path)
# If the 'repo' path is not really a git repository,
# then use that path as a prefix for patched files.
paths_prefix = None
if not git.is_repo_root(absolute_repo_path):
absolute_repo_path = project_root
paths_prefix = relative_repo_path
patches_data = config_contents['patches']
base_directory = os.path.dirname(self.path)
patches = [self.__create_patch(data, base_directory, repo_path) for data in patches_data]
patches = [self.__create_patch(data, base_directory, absolute_repo_path, paths_prefix) for data in patches_data]
patches_list = PatchesList(patches)
return patches_list

Просмотреть файл

@ -38,7 +38,7 @@ def main():
def apply_patches(repo_path, patches_paths, force=False, reverse=False):
patches = [Patch(patch_path, repo_path) for patch_path in patches_paths]
patches = [Patch(os.path.abspath(patch_path), os.path.abspath(repo_path)) for patch_path in patches_paths]
patches_list = PatchesList(patches)
stop_on_error = not force
return patches_list.apply(reverse=reverse, stop_on_error=stop_on_error)