Skip to content

Commit

Permalink
Merge pull request #14 from sommersoft/cli_args
Browse files Browse the repository at this point in the history
Updates: Command Line Arguments, am vs apply choice, New pylintrc patchfile, README updates
  • Loading branch information
tannewt committed Aug 20, 2018
2 parents d2c6ffd + e290ed0 commit 44d3b57
Show file tree
Hide file tree
Showing 4 changed files with 165 additions and 58 deletions.
38 changes: 30 additions & 8 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -117,15 +117,34 @@ To apply a patch to all CircuitPython libraries (only guaranteed for files share
among all libraries, such as those included in the cookiecutter (e.g. README.rst,
.travis.yml, etc), do the following:

1. Apply your update(s) to any library as normal, using ``git.commit``.
1. Apply your update(s) to any library as normal, using ``git commit``. It is recommended to
give a short, detailed description of the patch. This description will be used by the next
step for both the name of the patchfile and the subsequent patch messages.

2. Create a patch file using `git format-patch <https://git-scm.com/docs/git-format-patch>`_.
There are many techniques to using `git format-patch`; choose the one that makes
sense for your updates. ``--signoff`` is not necessary; adabot will force a
``--signoff`` when she uses ``git am``.
3. Place the patch file into the ``adabot/patches`` directory, and ``git commit`` with a
description of the patch(es).
4. Push the update to the adabot repository.
5. Run the patch update script.
sense for your updates. As a general usage example, ``format-patch -n`` will create patches
for ``n`` number of commits starting with the most recent:

.. code-block:: shell
# creates a patch file based on the last commit
git format-patch -1
# creates patch files based on the last 5 commits
git format-patch -5
# creates a patch file with zero lines of context (to eliminate any unique
# text that will cause the patch to not be applicable). must use
# 'git apply --unidiff-zero' flag to apply the patch.
git format-patch -1 -U0
3. Place the new patch file into the ``adabot/patches`` directory on a fork of the
adafruit/adabot repository, and ``git commit`` with a description of the patch(es).

4. Submit a Pull Request (PR) to the adafruit/adabot repository from the updated fork.

5. Run the patch update script after the PR has been merged.


To run the patch update script you must be inside this cloned adabot directory and
Expand All @@ -135,9 +154,12 @@ run the following command:
# note: ensure the local clone is current with the github repo that contains the patch(es)
# by using git pull before running the script.
python3 -m adabot.circuitpython_library_patches
# The 'circuitpython_library_patches' script accepts command line arguments. Use
# the help argument to display usage.
python3 -m adabot.circuitpython_library_patches -h
Contributing
============

Expand Down
133 changes: 110 additions & 23 deletions adabot/circuitpython_library_patches.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import json
import requests
import os
import sys
import argparse
import shutil
import sh
from sh.contrib import git
Expand All @@ -15,6 +17,25 @@
apply_errors = []
stats = []

"""
Setup the command line argument parsing object.
"""
cli_parser = argparse.ArgumentParser(description="Apply patches to any common file(s) in"
" all Adafruit CircuitPython Libraries.")
cli_parser.add_argument("-l", "--list", help="Lists the available patches to run.",
action='store_true')
cli_parser.add_argument("-p", help="Runs only the single patch referenced.",
metavar="<PATCH FILENAME>", dest="patch")
cli_parser.add_argument("-f", help="Adds the referenced FLAGS to the git.am call."
" Only available when using '-p'. Enclose flags in brackets '[]'."
" Multiple flags can be passed. NOTE: '--signoff' is already used "
" used by default, and will be ignored. EXAMPLE: -f [-C0] -f [-s]",
metavar="FLAGS", action="append", dest="flags", type=str)
cli_parser.add_argument("--use-apply", help="Forces use of 'git apply' instead of 'git am'."
" This is necessary when needing to use 'apply' flags not available"
" to 'am' (e.g. '--unidiff-zero'). Only available when using '-p'.",
action="store_true", dest="use_apply")

def get_repo_list():
""" Uses adabot.circuitpython_libraries module to get a list of
CircuitPython repositories. Filters the list down to adafruit
Expand All @@ -36,48 +57,77 @@ def get_patches():
"""
return_list = []
contents = requests.get("https://api.github.com/repos/adafruit/adabot/contents/patches")

if contents.ok:
for patch in contents.json():
patch_name = patch["name"]
return_list.append(patch_name)

return return_list

def apply_patch(repo_directory, patch_filepath, repo, patch):
def apply_patch(repo_directory, patch_filepath, repo, patch, flags, use_apply):
""" Apply the `patch` in `patch_filepath` to the `repo` in
`repo_directory` using git am. --signoff will sign the commit
`repo_directory` using git am or git apply. The commit
with the user running the script (adabot if credentials are set
for that).
When `use_apply` is true, the `--apply` flag is automatically added
to ensure that any passed flags that turn off apply (e.g. `--check`)
are overridden.
"""
if not os.getcwd() == repo_directory:
os.chdir(repo_directory)

try:
git.am("--signoff", patch_filepath)
except sh.ErrorReturnCode as Err:
apply_errors.append(dict(repo_name=repo,
patch_name=patch, error=Err.stderr))
return False
if not use_apply:
try:
git.am(flags, patch_filepath)
except sh.ErrorReturnCode as Err:
apply_errors.append(dict(repo_name=repo,
patch_name=patch, error=Err.stderr))
return False
else:
apply_flags = ["--apply"]
for flag in flags:
if not flag == "--signoff":
apply_flags.append(flag)
try:
git.apply(apply_flags, patch_filepath)
except sh.ErrorReturnCode as Err:
apply_errors.append(dict(repo_name=repo,
patch_name=patch, error=Err.stderr))
return False

with open(patch_filepath) as f:
for line in f:
if "[PATCH]" in line:
message = '"' + line[(line.find("]") + 2):] + '"'
break
try:
git.commit("-a", "-m", message)
except sh.ErrorReturnCode as Err:
apply_errors.append(dict(repo_name=repo,
patch_name=patch, error=Err.stderr))
return False

try:
git.push()
except sh.ErrorReturnCode as Err:
apply_errors.append(dict(repo_name=repo,
patch_name=patch, error=Err.stderr))
return False

return True

def check_patches(repo):
def check_patches(repo, patches, flags, use_apply):
""" Gather a list of patches from the `adabot/patches` directory
on the adabot repo. Clone the `repo` and run git apply --check
to test wether it requires any of the gathered patches.
When `use_apply` is true, any flags except `--apply` are passed
through to the check call. This ensures that the check call is
representative of the actual apply call.
"""
applied = 0
skipped = 0
failed = 0
patches = get_patches()

repo_directory = lib_directory + repo["name"]

Expand All @@ -100,19 +150,31 @@ def check_patches(repo):
patch_filepath = patch_directory + patch

try:
git.apply("--check", patch_filepath)
check_flags = ["--check"]
if use_apply:
for flag in flags:
if not flag in ("--apply", "--signoff"):
check_flags.append(flag)
git.apply(check_flags, patch_filepath)
run_apply = True
except sh.ErrorReturnCode_1:
except sh.ErrorReturnCode_1 as Err:
run_apply = False
skipped += 1
except sh.ErrorReturnCode_128 as Err:
if not b"error" in Err.stderr:
skipped += 1
else:
failed += 1
check_errors.append(dict(repo_name=repo["name"],
patch_name=patch, error=Err.stderr))

except sh.ErrorReturnCode as Err:
run_apply = False
failed += 1
check_errors.append(dict(repo_name=repo["name"],
patch_name=patch, error=Err.stderr))

if run_apply:
result = apply_patch(repo_directory, patch_filepath, repo["name"], patch)
result = apply_patch(repo_directory, patch_filepath, repo["name"],
patch, flags, use_apply)
if result:
applied += 1
else:
Expand All @@ -121,6 +183,31 @@ def check_patches(repo):
return [applied, skipped, failed]

if __name__ == "__main__":

run_patches = get_patches()
flags = ["--signoff"]

cli_args = cli_parser.parse_args()
if cli_args.list:
print("Available Patches:", run_patches)
sys.exit()
if cli_args.patch:
if not cli_args.patch in run_patches:
raise ValueError("'{}' is not an available patchfile.".format(cli_args.patch))
run_patches = [cli_args.patch]
if not cli_args.flags == None:
if not cli_args.patch:
raise RuntimeError("Must be used with a single patch. See help (-h) for usage.")
if "[-i]" in cli_args.flags:
raise ValueError("Interactive Mode flag not allowed.")
for flag in cli_args.flags:
if not flag == "[--signoff]":
flags.append(flag.strip("[]"))
if cli_args.use_apply:
if not cli_args.patch:
raise RuntimeError("Must be used with a single patch. See help (-h) for usage.")
use_apply = cli_args.use_apply

print(".... Beginning Patch Updates ....")
print(".... Working directory:", working_directory)
print(".... Library directory:", lib_directory)
Expand All @@ -136,23 +223,23 @@ def check_patches(repo):
for lib in libs:
shutil.rmtree(lib_directory + lib)
except FileNotFoundError:
pass
pass

repos = get_repo_list()
print(".... Running Patch Checks On", len(repos), "Repos ....")

for repo in repos:
results = check_patches(repo)
for k in range(len(stats)):
stats[k] += results[k]
results = check_patches(repo, run_patches, flags, use_apply)
for k in range(3):
stats[k] += results[k]

print(".... Patch Updates Completed ....")
print(".... Patches Applied:", stats[0])
print(".... Patches Skipped:", stats[1])
print(".... Patches Failed:", stats[2], "\n")
print(".... Patch Check Failure Report ....")
if len(check_errors) > 0:
for error, _ in check_errors:
for error in check_errors:
print(">>", error)
else:
print("No Failures")
Expand All @@ -163,4 +250,4 @@ def check_patches(repo):
print(">>", error)
else:
print("No Failures")

This file was deleted.

25 changes: 25 additions & 0 deletions patches/ignore-the-board-module-imports-in-.pylintrc.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
From b5290f3869789fbf8a57004003c94131cdad82c5 Mon Sep 17 00:00:00 2001
From: sommersoft <sommersoft@gmail.com>
Date: Tue, 14 Aug 2018 20:01:03 -0500
Subject: [PATCH] ignore the board module imports in .pylintrc

---
.pylintrc | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.pylintrc b/.pylintrc
index 946d694..cb8d23d 100644
--- a/.pylintrc
+++ b/.pylintrc
@@ -155,7 +155,7 @@ ignored-classes=optparse.Values,thread._local,_thread._local
# (useful for modules/projects where namespaces are manipulated during runtime
# and thus existing member attributes cannot be deduced by static analysis. It
# supports qualified module names, as well as Unix pattern matching.
-ignored-modules=
+ignored-modules=board

# Show a hint with possible names when a member name was not found. The aspect
# of finding the hint is based on edit distance.
--
2.15.1.windows.2

0 comments on commit 44d3b57

Please sign in to comment.