diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..7c6cc5d --- /dev/null +++ b/.editorconfig @@ -0,0 +1,63 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true + +[*.yaml] +indent_style = space +indent_size = 2 + +# 4 space indentation +[*.py] +# Isort definitions +multi_line_output=3 +include_trailing_comma=True +force_grid_wrap=0 +use_parentheses=True +indent_style = space +indent_size = 4 +max_line_length = 119 +line_length=119 +indent=' ' +balanced_wrapping=True +length_sort=1 +combine_as_imports = true +import_heading_future=General libraries +import_heading_localfolder=Project libraries +import_heading_thirdparty=Third party libraries +default_section=THIRDPARTY +no_lines_before=FUTURE,STDLIB +sections=FUTURE,STDLIB,THIRDPARTY,FIRSTPARTY,LOCALFOLDER + +# js files +[*.js] +indent_size = 2 +insert_final_newline = ignore +indent_style = space +trim_trailing_whitespace = true + +# The JSON files contain newlines inconsistently +[*.json] +indent_size = 2 +insert_final_newline = ignore + +# Minified JavaScript files shouldn't be changed +[**.min.js] +indent_style = ignore +insert_final_newline = ignore + +# Batch files use tabs for indentation +[*.bat] +indent_style = tab + +# Tab indentation (no size specified) +[Makefile] +indent_style = tab diff --git a/.gitignore b/.gitignore index bbedbc1..05c4bb2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ .python-version *.swp *.swo +.direnv/ +.envrc +gitfeatures.egg-info/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..814feed --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,64 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +--- +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v3.4.0 + hooks: + - id: check-added-large-files + # - id: check-docstring-first + - id: check-merge-conflict + - id: check-symlinks + - id: check-vcs-permalinks + # - id: check-xml + - id: debug-statements + # - id: detect-private-key + - id: trailing-whitespace + - id: end-of-file-fixer + - id: fix-byte-order-marker + - id: mixed-line-ending # force UNIX file endings for consistency + args: [--fix=lf] + - id: pretty-format-json + args: [--autofix, --indent, "2", --no-ensure-ascii, --no-sort-keys] + exclude: | + (?x)( + ^Pipfile\.lock$| + /fixtures/.*\.json$ + ) + - id: check-executables-have-shebangs + - id: requirements-txt-fixer + args: [requirements-dev.txt] + - repo: https://github.com/adrienverge/yamllint.git + rev: v1.26.0 + hooks: + - id: yamllint + - repo: https://github.com/pycqa/isort + rev: 5.8.0 + hooks: + - id: isort + name: isort (python) + - repo: https://github.com/psf/black + rev: 20.8b1 + hooks: + - id: black + language_version: python3 # Should be a command that runs python3.6+ + exclude: .+\/migrations.*\.py$ + args: [-t, py37, -l, "119"] + - repo: https://gitlab.com/pycqa/flake8 + rev: 3.8.4 + hooks: + - id: flake8 + exclude: .+\/migrations.*\.py$ + - repo: https://github.com/martyzz1/pre-commit-hooks + rev: v1.1 + hooks: + - id: prepend-branch-name + - repo: https://github.com/jumanjihouse/pre-commit-hooks + rev: 2.1.5 + hooks: + - id: shellcheck + - repo: https://github.com/Lucas-C/pre-commit-hooks-markup + rev: v1.0.1 + hooks: + - id: rst-linter + args: [--allow-raw] diff --git a/.shellcheckrc b/.shellcheckrc new file mode 100644 index 0000000..9b6800c --- /dev/null +++ b/.shellcheckrc @@ -0,0 +1 @@ +disable=SC2059,SC1091 diff --git a/.yamllint b/.yamllint new file mode 100644 index 0000000..f791804 --- /dev/null +++ b/.yamllint @@ -0,0 +1,12 @@ +--- + +yaml-files: + - '*.yaml' + - '*.yml' + - '.yamllint' + +rules: + line-length: + max: 119 + allow-non-breakable-words: true + allow-non-breakable-inline-mappings: true diff --git a/gitfeatures/core.py b/gitfeatures/core.py index 2b47e60..1840b58 100644 --- a/gitfeatures/core.py +++ b/gitfeatures/core.py @@ -1,30 +1,35 @@ -from subprocess import check_output, CalledProcessError -import datetime -import re -import webbrowser import os +import re import sys +import datetime +import webbrowser +from subprocess import CalledProcessError, check_output + from six.moves import input -master_branch = os.environ.get('GITFEATURES_MASTER_BRANCH', 'master') -repo = os.environ.get('GITFEATURES_REPO', 'github') -merge_strategy = os.environ.get('GITFEATURES_STRATEGY', 'merge') +master_branch = os.environ.get("GITFEATURES_MASTER_BRANCH", "master") +repo = os.environ.get("GITFEATURES_REPO", "github") +merge_strategy = os.environ.get("GITFEATURES_STRATEGY", "merge") def _call(args): try: - return check_output(args).decode('utf-8') + return check_output(args).decode("utf-8") except CalledProcessError: sys.exit(__name__ + ": none zero exit status executing: " + " ".join(args)) # noqa def new_feature(name, prefix): - name = re.sub('\W', '_', name) + name = re.sub(r"\W", "_", name) original_branch = _current_branch() if original_branch != master_branch: print(_current_branch(), master_branch) - print("You aren't on your main {} branch. Are you sure you wish to create a branch from {}? [y/n]".format(master_branch, original_branch)) # noqa - if input().lower() != 'y': + print( + "You aren't on your main {} branch. Are you sure you wish to create a branch from {}? [y/n]".format( + master_branch, original_branch + ) + ) # noqa + if input().lower() != "y": sys.exit("Ok, Exiting") # noqa _call(["git", "remote", "update", "origin"]) @@ -41,7 +46,7 @@ def finish_feature(name, prefix): cur_branch = _current_branch() if name: - branch = prefix + '_' + name + branch = prefix + "_" + name if branch == cur_branch: _call(["git", "checkout", master_branch]) elif cur_branch != master_branch: @@ -52,40 +57,56 @@ def finish_feature(name, prefix): _call(["git", "remote", "update", "origin"]) - commits = _call(["git", "log", '--oneline', branch, '^origin/{}'.format(master_branch)]) + commits = _call(["git", "log", "--oneline", branch, "^origin/{}".format(master_branch)]) if commits: sys.exit( - __name__ + ": " + branch - + " contains commits that are not in {}:\n".format(master_branch) + commits - + "\nraise a pull request and get them merged in.") + __name__ + + ": " + + branch + + " contains commits that are not in {}:\n".format(master_branch) + + commits + + "\nraise a pull request and get them merged in." + ) else: _call(["git", "push", "origin", ":" + branch]) _call(["git", "branch", "-D", branch]) -def stable(args): - if (len(args) > 0 and args[0] == 'new'): +def _branch_func(branch_type, args): + if len(args) > 0 and args[0] == "new": date = datetime.datetime.now() - new_branch = 'stable_{}'.format(date.strftime('%Y%m%d')) + new_branch = "{}_{}".format(branch_type, date.strftime("%Y%m%d")) _call(["git", "checkout", "-b", new_branch]) _call(["git", "push", "-u", "origin", new_branch + ":" + new_branch]) - stable_branches = _get_stable_branches() - if len(stable_branches) > 3: - print("you have more than 3 stable branches, shall I delete the eldest one? [y/n]") # noqa - if input().lower() == 'y': - branch = stable_branches[0] + branches = _get_branches(branch_type) + if len(branches) > 3: + print(f"you have more than 3 {branch_type} branches, shall I delete the eldest one? [y/n]") # noqa + if input().lower() == "y": + branch = branches[0] _call(["git", "push", "origin", "--delete", branch]) _call(["git", "branch", "-D", branch]) else: - # checkout the latest stable branch - stable_branches = _get_stable_branches() - if stable_branches: - branch = stable_branches[-1] + # checkout the latest branch + branches = _get_branches(branch_type) + if branches: + branch = branches[-1] _call(["git", "checkout", branch]) else: - print("No stable branches") + print(f"No {branch_type} branches") + + +def stable(args): + return _branch_func("stable", args) + + +def hotfix(args): + return _branch_func("hotfix", args) + + +def release(args): + return _branch_func("release", args) def pullrequest(args): @@ -94,41 +115,50 @@ def pullrequest(args): sys.exit(__name__ + ": can't issue pull requests on {}".format(master_branch)) # check its up to date with remote master if not pull - _call(['git', 'remote', 'update', 'origin']) - commits = _call(['git', 'log', '--oneline', '^' + branch, 'origin/{}'.format(master_branch)]) + _call(["git", "remote", "update", "origin"]) + commits = _call(["git", "log", "--oneline", "^" + branch, "origin/{}".format(master_branch)]) if commits: - print("Your branch is behind origin/{} so cannot be automatically {}d.".format(merge_strategy, master_branch)) # noqa + print( + "Your branch is behind origin/{} so cannot be automatically {}d.".format(merge_strategy, master_branch) + ) # noqa print(commits) - print("Do you wish to update and {} {} (If conflicts occur, you will be able to fix them)? [y/n]".format(merge_strategy, master_branch)) # noqa - if input().lower() == 'y': - _call(['git', 'checkout', master_branch]) - _call(['git', 'pull']) - _call(['git', 'checkout', branch]) + print( + "Do you wish to update and {} {} (If conflicts occur, you will be able to fix them)? [y/n]".format( + merge_strategy, master_branch + ) + ) # noqa + if input().lower() == "y": + _call(["git", "checkout", master_branch]) + _call(["git", "pull"]) + _call(["git", "checkout", branch]) try: print("git {} {}".format(merge_strategy, master_branch)) - output = check_output(['git', merge_strategy, master_branch]).decode('utf-8') + output = check_output(["git", merge_strategy, master_branch]).decode("utf-8") print(output) print("Congratulations, successfully {}d {}".format(merge_strategy, master_branch)) except CalledProcessError as e: - if 'CONFLICT' in e.output: - err = e.output + "\n\nUnlucky! You have work to do. Fix the above conflicts and run git pullrequest again" # noqa + if "CONFLICT" in e.output: + err = ( + e.output + + "\n\nUnlucky! You have work to do. Fix the above conflicts and run git pullrequest again" + ) # noqa sys.exit(err) else: - raise() + raise () # check if there are any unpushed commits - commits = _call(['git', 'log', '--oneline', branch, '^origin/' + branch]) + commits = _call(["git", "log", "--oneline", branch, "^origin/" + branch]) if commits: print("You have unpushed commits:") print(commits) print("Push commits to origin [y/n]") - if input().lower() == 'y': - _call(['git', 'push', 'origin', branch + ':' + branch]) + if input().lower() == "y": + _call(["git", "push", "origin", branch + ":" + branch]) origin = _call(["git", "config", "--get", "remote.origin.url"]) - name = origin.split(':')[1].replace(".git\n", '') + name = origin.split(":")[1].replace(".git\n", "") url = _get_pullrequest_url(name, branch) - if (len(args) > 0 and args[0] == '--dry-run') or os.environ.get('CONSOLEONLY', False): # noqa + if (len(args) > 0 and args[0] == "--dry-run") or os.environ.get("CONSOLEONLY", False): # noqa print(url) else: webbrowser.open_new_tab(url) @@ -136,16 +166,16 @@ def pullrequest(args): def _get_pullrequest_url(name, branch): - if repo == 'github': + if repo == "github": url = "https://github.com/" + name + "/pull/new/" + branch - elif repo == 'bitbucket': + elif repo == "bitbucket": url = "https://bitbucket.org/" + name + "/pull-requests/new?t=1&source=" + branch # noqa return url def _current_branch(): output = _call(["git", "branch"]) - branch = re.search('^\* (.+)$', output, flags=re.M).group(1) + branch = re.search(r"^\* (.+)$", output, flags=re.M).group(1) if not branch: sys.exit(__name__ + ": unable to detect current branch") else: @@ -154,15 +184,22 @@ def _current_branch(): def _branch_exists(name): branch_list = _call(["git", "branch", "-a"]) - return 1 if re.search('' + name + '$', branch_list, flags=re.M) else 0 + return 1 if re.search(r"" + name + "$", branch_list, flags=re.M) else 0 -def _get_stable_branches(): +def _get_branches(branch_type): _call(["git", "remote", "update", "origin"]) try: - branch_list = check_output("git branch -r | grep -e '\/stable_\d\d\d\d\d\d\d\d'", shell=True).decode('utf-8').strip() # noqa - branch_list = branch_list.split('\n') - branch_list = list(map(lambda it: it.split('/')[1].strip(), branch_list)) + branch_list = ( + check_output( + f"git branch -r | grep -e '\/{branch_type}_\d\d\d\d\d\d\d\d'", # noqa + shell=True, + ) + .decode("utf-8") + .strip() + ) + branch_list = branch_list.split("\n") + branch_list = list(map(lambda it: it.split("/")[1].strip(), branch_list)) return branch_list except CalledProcessError: @@ -170,8 +207,9 @@ def _get_stable_branches(): def run(prefix, args): - if len(args) and args[0].lower() == 'new': - if prefix == 'releasecandidate': + if len(args) and args[0].lower() == "new": + allowed_branch_types = ["releasecandidate", "stable", "release", "hotfix"] + if prefix in allowed_branch_types: if len(args) == 2: new_feature(args[1], prefix) else: @@ -181,7 +219,7 @@ def run(prefix, args): new_feature(args[1], prefix) else: sys.exit("Usage: git %s new <%s_name>" % (prefix, prefix)) - elif len(args) and args[0].lower() == 'finish': + elif len(args) and args[0].lower() == "finish": if len(args) == 1: finish_feature(None, prefix) elif len(args) == 2: diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000..3e813a7 --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,4 @@ +black==21.5b0 +flake8==3.9.2 +https://github.com/robinedwards/gitfeatures/tarball/v0.0.6#egg=gitfeatures +isort==5.8.0 diff --git a/scripts/git-feature b/scripts/git-feature index 8afb0da..ecef64e 100755 --- a/scripts/git-feature +++ b/scripts/git-feature @@ -1,6 +1,6 @@ #!/usr/bin/env python -import gitfeatures import sys +import gitfeatures -gitfeatures.run('feature', sys.argv[1:]) +gitfeatures.run("feature", sys.argv[1:]) diff --git a/scripts/git-hotfix b/scripts/git-hotfix index c45f41c..4f99a71 100755 --- a/scripts/git-hotfix +++ b/scripts/git-hotfix @@ -1,6 +1,6 @@ #!/usr/bin/env python -import gitfeatures import sys +import gitfeatures -gitfeatures.run('hotfix', sys.argv[1:]) +gitfeatures.hotfix(sys.argv[1:]) diff --git a/scripts/git-pullrequest b/scripts/git-pullrequest index cd41676..e60465f 100755 --- a/scripts/git-pullrequest +++ b/scripts/git-pullrequest @@ -1,6 +1,6 @@ #!/usr/bin/env python -import gitfeatures import sys +import gitfeatures gitfeatures.pullrequest(sys.argv[1:]) diff --git a/scripts/git-release b/scripts/git-release new file mode 100755 index 0000000..81a223c --- /dev/null +++ b/scripts/git-release @@ -0,0 +1,6 @@ +#!/usr/bin/env python +import sys + +import gitfeatures + +gitfeatures.release(sys.argv[1:]) diff --git a/scripts/git-releasecandidate b/scripts/git-releasecandidate index 695b31a..f0b9c48 100755 --- a/scripts/git-releasecandidate +++ b/scripts/git-releasecandidate @@ -1,6 +1,6 @@ #!/usr/bin/env python -import gitfeatures import sys +import gitfeatures -gitfeatures.run('releasecandidate', sys.argv[1:]) +gitfeatures.run("releasecandidate", sys.argv[1:]) diff --git a/scripts/git-stable b/scripts/git-stable index 559f9b8..49564a9 100755 --- a/scripts/git-stable +++ b/scripts/git-stable @@ -1,6 +1,6 @@ #!/usr/bin/env python -import gitfeatures import sys +import gitfeatures gitfeatures.stable(sys.argv[1:]) diff --git a/setup.py b/setup.py index fd5cf4f..3cfaab3 100644 --- a/setup.py +++ b/setup.py @@ -1,26 +1,21 @@ from setuptools import setup -long_description = "" -try: - long_description = open("README.rst", "r").read() -except: - pass - +readme = open("README.rst").read() +long_description = readme setup( name="gitfeatures", - version="0.0.6", - packages=['gitfeatures'], + version="0.1.0", + packages=["gitfeatures"], license="MIT", long_description=long_description, - install_requires=[ - 'six' - ], + install_requires=["six"], scripts=[ - 'scripts/git-feature', - 'scripts/git-hotfix', - 'scripts/git-stable', - 'scripts/git-pullrequest', - 'scripts/git-releasecandidate' - ] + "scripts/git-feature", + "scripts/git-hotfix", + "scripts/git-release", + "scripts/git-stable", + "scripts/git-pullrequest", + "scripts/git-releasecandidate", + ], ) diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..ca6dfe1 --- /dev/null +++ b/tox.ini @@ -0,0 +1,5 @@ +[flake8] +ignore = C901,E202,E231,E124,F403,W503,D +max-line-length = 119 +max-complexity = 10 +exclude = .svn,CVS,.bzr,.hg,.git,__pycache__,.tox,.eggs,*.egg,