diff --git a/gitplus/cmd_git_multi.py b/gitplus/cmd_git_multi.py index 32dfcec..6e2c44d 100755 --- a/gitplus/cmd_git_multi.py +++ b/gitplus/cmd_git_multi.py @@ -23,199 +23,201 @@ from typing import * -# Prefix for git command output: -PREFIX = '\t' -args = sys.argv[1:] -# If true, projects with same git command output will be groupped together: -group_by_output = True +def main(): + # Prefix for git command output: + PREFIX = '\t' -# -c switch in command line, if true execute only if project not changed (i.e. -# the `git --porcelain` output is empty: -only_if_changed = False + args = sys.argv[1:] + # If true, projects with same git command output will be groupped together: + group_by_output = True + # -c switch in command line, if true execute only if project not changed (i.e. + # the `git --porcelain` output is empty: + only_if_changed = False -def is_ignored(project: str) -> bool: - return project in ignore_projects + def is_ignored(project: str) -> bool: + return project in ignore_projects -def process_dir(dirname: str, depth: int) -> None: - abs_dir_name = os.path.abspath(dirname) - for file_name in os.listdir(dirname): - if not single_project or single_project == file_name: - os.chdir(abs_dir_name) - if path.isdir(file_name): - if path.exists('%s/.git' % file_name): - process_project(abs_dir_name, file_name) + def process_dir(dirname: str, depth: int) -> None: + abs_dir_name = os.path.abspath(dirname) + for file_name in os.listdir(dirname): + if not single_project or single_project == file_name: + os.chdir(abs_dir_name) - if show_progress and group_by_output: - print('*', end=' ') - sys.stdout.flush() - elif depth > 1: - process_dir(os.path.join(abs_dir_name, file_name), depth - 1) + if path.isdir(file_name): + if path.exists('%s/.git' % file_name): + process_project(abs_dir_name, file_name) + if show_progress and group_by_output: + print('*', end=' ') + sys.stdout.flush() + elif depth > 1: + process_dir(os.path.join(abs_dir_name, file_name), depth - 1) -def process_project(current_path: str, file_name: str) -> None: - if is_ignored(file_name): - return - dir_path = '%s/%s' % (current_path, file_name) - os.chdir(dir_path) + def process_project(current_path: str, file_name: str) -> None: + if is_ignored(file_name): + return - output = '' + dir_path = '%s/%s' % (current_path, file_name) + os.chdir(dir_path) - if group_by_output: - do_output = False - else: - do_output = True - - if not group_by_output: - print('%s:' % file_name) - - execute = True - if only_if_changed: - execute = git.is_changed() - if not execute: - output = PREFIX + 'Not changed' - if do_output: - print(output) - - if execute: - executed, result = git.execute_command(git_command, output=do_output, prefix=PREFIX, grep=grep) - result = result.rstrip() - - if not executed: - output = '\tError executing:%s:%s' % (git_command, result) - elif result: - output = result - else: - output = '\tOK' + output = '' - if group_by_output: - if output in outputs: - outputs[output].append(file_name) + if group_by_output: + do_output = False else: - outputs[output] = [file_name] - -show_progress = True - -# If true, projects with same git command output will be groupped together: -group_by_output = False - -# Check if to group results by output: -# Set by: -# git config --global 'multi.groupbyoutput' 1 -# Unset by: -# git config --global --unset 'multi.groupbyoutput' -config_properties = git.get_config_properties() -group_by_output_config_key = 'multi.groupbyoutput' -if group_by_output_config_key in config_properties: - group_by_output_config_value = config_properties[group_by_output_config_key] - - if group_by_output_config_value: - if group_by_output_config_value.lower() in ('true', '1'): - group_by_output = True - -# Check if to do backup of projects: -backup = utils.has_arg(args, 'a', 'archive') - -# Check if needed to be executed on only one project: -single_project = utils.get_arg(args, 'p', 'project') -if single_project and single_project[-1] == '/': - single_project = single_project[: -1] - -# Check projects which we don't need to be part of git multi commands execution: -ignore_projects: List[str] = [] -except_projects = utils.get_arg(args, 'e', 'except') -if except_projects: - ignore_projects = [x.strip() for x in except_projects.split(",")] - -# Check if to execute only on projects which contain some changes: -only_if_changed = utils.has_arg(args, 'c', 'changed') - -# Output only the current branch for all projects: -branch = utils.has_arg(args, 'b', 'branch') -branch_verbose = utils.has_arg(args, 'bv', 'branch-verbose') - -# Specify depth for recursive search -depth = int(utils.get_arg(args, 'd', 'depth') or "1") - -# Compute projects to be executed: -if path.exists('.multigit_ignore'): - f = open('.multigit_ignore') - for line in f: - project = line.strip() - ignore_projects.append(project) - print('Ignoring:', ', '.join(ignore_projects)) - f.close() - -# Execute backup? -if backup: - now = datetime.datetime.now().strftime('%Y-%m-%d-%H-%M') - tar_name = 'git-repositories-%s.tar' % (now) - - executed, output = git.execute_command('tar cvf %s */.git */.gitignore' % tar_name, output=True) - - print('Saved git repositories to %s' % tar_name) - - sys.exit(0) + do_output = True + + if not group_by_output: + print('%s:' % file_name) + + execute = True + if only_if_changed: + execute = git.is_changed() + if not execute: + output = PREFIX + 'Not changed' + if do_output: + print(output) + + if execute: + executed, result = git.execute_command(git_command, output=do_output, prefix=PREFIX, grep=grep) + result = result.rstrip() + + if not executed: + output = '\tError executing:%s:%s' % (git_command, result) + elif result: + output = result + else: + output = '\tOK' + + if group_by_output: + if output in outputs: + outputs[output].append(file_name) + else: + outputs[output] = [file_name] + + show_progress = True + + # If true, projects with same git command output will be groupped together: + group_by_output = False + + # Check if to group results by output: + # Set by: + # git config --global 'multi.groupbyoutput' 1 + # Unset by: + # git config --global --unset 'multi.groupbyoutput' + config_properties = git.get_config_properties() + group_by_output_config_key = 'multi.groupbyoutput' + if group_by_output_config_key in config_properties: + group_by_output_config_value = config_properties[group_by_output_config_key] + + if group_by_output_config_value: + if group_by_output_config_value.lower() in ('true', '1'): + group_by_output = True + + # Check if to do backup of projects: + backup = utils.has_arg(args, 'a', 'archive') + + # Check if needed to be executed on only one project: + single_project = utils.get_arg(args, 'p', 'project') + if single_project and single_project[-1] == '/': + single_project = single_project[: -1] + + # Check projects which we don't need to be part of git multi commands execution: + ignore_projects: List[str] = [] + except_projects = utils.get_arg(args, 'e', 'except') + if except_projects: + ignore_projects = [x.strip() for x in except_projects.split(",")] + + # Check if to execute only on projects which contain some changes: + only_if_changed = utils.has_arg(args, 'c', 'changed') + + # Output only the current branch for all projects: + branch = utils.has_arg(args, 'b', 'branch') + branch_verbose = utils.has_arg(args, 'bv', 'branch-verbose') + + # Specify depth for recursive search + depth = int(utils.get_arg(args, 'd', 'depth') or "1") + + # Compute projects to be executed: + if path.exists('.multigit_ignore'): + f = open('.multigit_ignore') + for line in f: + project = line.strip() + ignore_projects.append(project) + print('Ignoring:', ', '.join(ignore_projects)) + f.close() + + # Execute backup? + if backup: + now = datetime.datetime.now().strftime('%Y-%m-%d-%H-%M') + tar_name = 'git-repositories-%s.tar' % (now) + + executed, output = git.execute_command('tar cvf %s */.git */.gitignore' % tar_name, output=True) + + print('Saved git repositories to %s' % tar_name) + + sys.exit(0) + + grep = None + + # If no specific command is entered (i.e. only 'git multi'): + git_command = ['status', '-s'] + if args: + git_command = args + + if branch_verbose: + git_command = ['branch', '-v'] + group_by_output = True + grep = '* ' + elif branch: + git_command = ['branch'] + grep = '* ' + + if git_command[0] == 'gitk': + git_command = git_command + else: + git_command.insert(0, 'git') -grep = None + print('--------------------------------------------------------------------------------') + if grep: + print('Executing: %s | grep %s' % (' '.join(git_command), grep)) + else: + print('Executing %s' % ' '.join(git_command)) + print('--------------------------------------------------------------------------------') -# If no specific command is entered (i.e. only 'git multi'): -git_command = ['status', '-s'] -if args: - git_command = args + # Used when group_by_output == True, keys are output, values are a list of projects + outputs: Dict[str, List[str]] = {} -if branch_verbose: - git_command = ['branch', '-v'] - group_by_output = True - grep = '* ' -elif branch: - git_command = ['branch'] - grep = '* ' - -if git_command[0] == 'gitk': - git_command = git_command -else: - git_command.insert(0, 'git') - -print('--------------------------------------------------------------------------------') -if grep: - print('Executing: %s | grep %s' % (' '.join(git_command), grep)) -else: - print('Executing %s' % ' '.join(git_command)) -print('--------------------------------------------------------------------------------') - -# Used when group_by_output == True, keys are output, values are a list of projects -outputs: Dict[str, List[str]] = {} - -if show_progress and group_by_output: - print('Progress:', end=' ') - sys.stdout.flush() - -# Execute command on all subdirectories: -process_dir(os.getcwd(), depth) - -if show_progress and group_by_output: - print() - print('--------------------------------------------------------------------------------') + if show_progress and group_by_output: + print('Progress:', end=' ') + sys.stdout.flush() -# If group_by_output, then output them now: -if group_by_output: - result_outputs = [] - output_keys = list(outputs.keys()) - output_keys.sort() - for output in output_keys: - projects = outputs[output] - projects.sort() + # Execute command on all subdirectories: + process_dir(os.getcwd(), depth) - result_outputs.append([', '.join(projects) + ':', output]) + if show_progress and group_by_output: print() + print('--------------------------------------------------------------------------------') - result_outputs.sort() - for key, value in result_outputs: - print(key) - print(value) - print() + # If group_by_output, then output them now: + if group_by_output: + result_outputs = [] + output_keys = list(outputs.keys()) + output_keys.sort() + for output in output_keys: + projects = outputs[output] + projects.sort() + + result_outputs.append([', '.join(projects) + ':', output]) + print() + + result_outputs.sort() + for key, value in result_outputs: + print(key) + print(value) + print() diff --git a/gitplus/cmd_git_old_branches.py b/gitplus/cmd_git_old_branches.py index 6b99728..35ce293 100755 --- a/gitplus/cmd_git_old_branches.py +++ b/gitplus/cmd_git_old_branches.py @@ -21,66 +21,68 @@ from . import git -git.assert_in_git_repository() - -parser = argparse.ArgumentParser( - description='Detects (and possibly deletes) old unused branches') - -parser.add_argument('-n', '--no-merged', action='store_true', - default=False, help='Only *not* merged branches') -parser.add_argument('-m', '--merged', action='store_true', - default=False, help='Only merged branches') -parser.add_argument('-r', '--remote', action='store_true', - default=False, help='Get remote branches') -parser.add_argument('-a', '--all', action='store_true', - default=False, help='Get all (local and remote) branches') -parser.add_argument('-d', '--days', default=180, type=int, - help='How many days old must a branch be') -parser.add_argument('--delete', action='store_true', default=False, - help='Remove branches older than ... days (you\'ll be asked to confirm the deletion)') - -args = parser.parse_args() - -now = time.time() - -remote: bool = args.remote -all_branches: bool = args.all -merged: bool = args.merged -no_merged: bool = args.no_merged - -branches = git.get_branches(remote, all_branches, merged=merged, no_merged=no_merged) -delete_all = False -for branch in branches: - if remote and not branch.startswith("remotes/"): - branch = f"remotes/{branch}" - command = 'log ' + branch + ' -1 --format=%at --' - success, result = git.execute_git(command, output=False) - if not success: - sys.stderr.write(command) - sys.stderr.write(result) - sys.exit(1) - - if len(result.strip()) == 0: - print('Cannot find the age of %s' % branch) - else: - time_diff_seconds = int(now) - int(result) - time_diff_days = int((float(time_diff_seconds) / (60*60*24)) * 100) / 100. - - #print 'Last commit in %s was %s days ago' % (branch, time_diff_days) - - if time_diff_days > args.days: - print('Branch %s is older than %s days (%s)!' % (branch, args.days, time_diff_days)) - if args.delete: - if delete_all: - answer = 'y' - else: - answer = input('Remove [y/N/all] ?') - - if answer.lower() == 'all' and input(f"Really delete all filtered branches [y/N]?") == 'y': - delete_all = True - answer = 'y' - - if answer.lower() == 'y': - git.delete_branch(branch, force=True) - else: - print('Not deleted') + +def main(): + git.assert_in_git_repository() + + parser = argparse.ArgumentParser( + description='Detects (and possibly deletes) old unused branches') + + parser.add_argument('-n', '--no-merged', action='store_true', + default=False, help='Only *not* merged branches') + parser.add_argument('-m', '--merged', action='store_true', + default=False, help='Only merged branches') + parser.add_argument('-r', '--remote', action='store_true', + default=False, help='Get remote branches') + parser.add_argument('-a', '--all', action='store_true', + default=False, help='Get all (local and remote) branches') + parser.add_argument('-d', '--days', default=180, type=int, + help='How many days old must a branch be') + parser.add_argument('--delete', action='store_true', default=False, + help='Remove branches older than ... days (you\'ll be asked to confirm the deletion)') + + args = parser.parse_args() + + now = time.time() + + remote: bool = args.remote + all_branches: bool = args.all + merged: bool = args.merged + no_merged: bool = args.no_merged + + branches = git.get_branches(remote, all_branches, merged=merged, no_merged=no_merged) + delete_all = False + for branch in branches: + if remote and not branch.startswith("remotes/"): + branch = f"remotes/{branch}" + command = 'log ' + branch + ' -1 --format=%at --' + success, result = git.execute_git(command, output=False) + if not success: + sys.stderr.write(command) + sys.stderr.write(result) + sys.exit(1) + + if len(result.strip()) == 0: + print('Cannot find the age of %s' % branch) + else: + time_diff_seconds = int(now) - int(result) + time_diff_days = int((float(time_diff_seconds) / (60*60*24)) * 100) / 100. + + #print 'Last commit in %s was %s days ago' % (branch, time_diff_days) + + if time_diff_days > args.days: + print('Branch %s is older than %s days (%s)!' % (branch, args.days, time_diff_days)) + if args.delete: + if delete_all: + answer = 'y' + else: + answer = input('Remove [y/N/all] ?') + + if answer.lower() == 'all' and input(f"Really delete all filtered branches [y/N]?") == 'y': + delete_all = True + answer = 'y' + + if answer.lower() == 'y': + git.delete_branch(branch, force=True) + else: + print('Not deleted') diff --git a/gitplus/cmd_git_recent.py b/gitplus/cmd_git_recent.py index f13045d..c2692fa 100755 --- a/gitplus/cmd_git_recent.py +++ b/gitplus/cmd_git_recent.py @@ -21,111 +21,113 @@ from typing import * -git.assert_in_git_repository() - -parser = mod_argparse.ArgumentParser( - description='Show list of branches sorted by last commit time') - -parser.add_argument('-r', '--remote', action='store_true', - default=False, help='Get remote branches') -parser.add_argument('-b', '--brief', action='store_true', - default=False, help='brief output') -parser.add_argument('-a', '--all', action='store_true', - default=False, help='Get all (local and remote) branches') -parser.add_argument('-n', '--no-merged', action='store_true', - default=False, help='Only *not* merged branches') -parser.add_argument('-m', '--merged', action='store_true', - default=False, help='Only merged branches') -parser.add_argument('entries', metavar='entries', type=int, default=1000, nargs='?', - help='Number of entries (negative number if you want the last N entries)') -parser.add_argument('-x', '--execute-command', help='Execute command with branch name') -parser.add_argument('-ch', '--checkout', action='store_true', - default=False, help='Checkout to branch') -parser.add_argument('-chn', '--checkoutnth', - default=False, help='Checkout to n-th branch') -parser.add_argument('-me', '--merge', action='store_true', - default=False, help='Merge recent branch') -parser.add_argument('-men', '--mergenth', action='store_true', - default=False, help='Merge nth recent branch') - -args = parser.parse_args() - -now = mod_time.time() - -times_and_branches = [] - -brief: bool = args.brief -merged: bool = args.merged -remote: bool = args.remote -get_all: bool = args.all -no_merged: bool = args.no_merged -execute_command = args.execute_command -merge = args.merge -merge_nth = args.mergenth - -if args.checkout: - execute_command = "checkout" - -nth_branch = execute_command or merge or merge_nth - -branches = git.get_branches(remote, merged=merged, no_merged=no_merged, all=get_all) -for branch in branches: - if "HEAD detached" in branch: - continue - cmd = 'log ' + branch + ' -1 --format=%at --' - success, result = git.execute_git(cmd, output=False) - if not success: - mod_sys.stderr.write(cmd) - mod_sys.stderr.write(result) - mod_sys.exit(1) - - time_diff_seconds = int(now) - int(result) - if (not success) or (len(result.strip()) == 0): - print('Cannot find the age of %s' % branch) - elif brief: - times_and_branches.append((time_diff_seconds, branch)) - else: - time_diff_days = int((float(time_diff_seconds) / (60*60*24)) * 100) / 100. - times_and_branches.append((time_diff_seconds, '%10s days: %s' % (time_diff_days, branch), )) - -times_and_branches.sort() - -try: - entries = int(args.entries) -except: - entries = 1000 - -if entries > 0: - times_and_branches = times_and_branches[:entries] -if entries < 0: - times_and_branches = times_and_branches[entries:] - -n = 0 -for _, branch in times_and_branches: - n += 1 - if nth_branch: - print(f" [{n}] {branch}") - else: - print(branch) -if nth_branch: - nth = None +def main(): + git.assert_in_git_repository() + + parser = mod_argparse.ArgumentParser( + description='Show list of branches sorted by last commit time') + + parser.add_argument('-r', '--remote', action='store_true', + default=False, help='Get remote branches') + parser.add_argument('-b', '--brief', action='store_true', + default=False, help='brief output') + parser.add_argument('-a', '--all', action='store_true', + default=False, help='Get all (local and remote) branches') + parser.add_argument('-n', '--no-merged', action='store_true', + default=False, help='Only *not* merged branches') + parser.add_argument('-m', '--merged', action='store_true', + default=False, help='Only merged branches') + parser.add_argument('entries', metavar='entries', type=int, default=1000, nargs='?', + help='Number of entries (negative number if you want the last N entries)') + parser.add_argument('-x', '--execute-command', help='Execute command with branch name') + parser.add_argument('-ch', '--checkout', action='store_true', + default=False, help='Checkout to branch') + parser.add_argument('-chn', '--checkoutnth', + default=False, help='Checkout to n-th branch') + parser.add_argument('-me', '--merge', action='store_true', + default=False, help='Merge recent branch') + parser.add_argument('-men', '--mergenth', action='store_true', + default=False, help='Merge nth recent branch') + + args = parser.parse_args() + + now = mod_time.time() + + times_and_branches = [] + + brief: bool = args.brief + merged: bool = args.merged + remote: bool = args.remote + get_all: bool = args.all + no_merged: bool = args.no_merged + execute_command = args.execute_command + merge = args.merge + merge_nth = args.mergenth + + if args.checkout: + execute_command = "checkout" + + nth_branch = execute_command or merge or merge_nth + + branches = git.get_branches(remote, merged=merged, no_merged=no_merged, all=get_all) + for branch in branches: + if "HEAD detached" in branch: + continue + cmd = 'log ' + branch + ' -1 --format=%at --' + success, result = git.execute_git(cmd, output=False) + if not success: + mod_sys.stderr.write(cmd) + mod_sys.stderr.write(result) + mod_sys.exit(1) + + time_diff_seconds = int(now) - int(result) + if (not success) or (len(result.strip()) == 0): + print('Cannot find the age of %s' % branch) + elif brief: + times_and_branches.append((time_diff_seconds, branch)) + else: + time_diff_days = int((float(time_diff_seconds) / (60*60*24)) * 100) / 100. + times_and_branches.append((time_diff_seconds, '%10s days: %s' % (time_diff_days, branch), )) + + times_and_branches.sort() + try: - if merge_nth: - nth = int(merge_nth) - if execute_command: - nth = int(input(f"Execute {execute_command} with branch (1-{n})? ")) - if merge: - nth = int(input(f"Merge (1-{n})? ")) + entries = int(args.entries) except: - mod_sys.exit(1) + entries = 1000 - if not nth: - mod_sys.exit(1) + if entries > 0: + times_and_branches = times_and_branches[:entries] + if entries < 0: + times_and_branches = times_and_branches[entries:] - branch = times_and_branches[nth-1][1].split(":")[1].strip() + n = 0 + for _, branch in times_and_branches: + n += 1 + if nth_branch: + print(f" [{n}] {branch}") + else: + print(branch) - if execute_command: - git.execute_git([execute_command, branch]) - if merge or merge_nth: - git.execute_git(["merge", branch]) \ No newline at end of file + if nth_branch: + nth = None + try: + if merge_nth: + nth = int(merge_nth) + if execute_command: + nth = int(input(f"Execute {execute_command} with branch (1-{n})? ")) + if merge: + nth = int(input(f"Merge (1-{n})? ")) + except: + mod_sys.exit(1) + + if not nth: + mod_sys.exit(1) + + branch = times_and_branches[nth-1][1].split(":")[1].strip() + + if execute_command: + git.execute_git([execute_command, branch]) + if merge or merge_nth: + git.execute_git(["merge", branch]) diff --git a/gitplus/cmd_git_relation.py b/gitplus/cmd_git_relation.py index bd7f3df..e8cedea 100755 --- a/gitplus/cmd_git_relation.py +++ b/gitplus/cmd_git_relation.py @@ -19,127 +19,129 @@ from . import git from . import semver -git.assert_in_git_repository() - -parser = argparse.ArgumentParser( - description='Show a relation between two git commits/tags/branches') - -parser.add_argument('branch_1', metavar='branch_1', type=str, default='HEAD', nargs='?', - help='the one commit/tag/branch') -parser.add_argument('branch_2', metavar='branch_2', type=str, nargs='?', default='HEAD', - help='the other commit/tag/branch (default HEAD)') -parser.add_argument('-b', '--brief', action='store_true', default=False, help='brief output') -parser.add_argument('-m', '--msg', action='store_true', - default=False, help='only commit messages') -parser.add_argument('-u', '--upstream', action='store_true', - default=False, help='relation with upstream branch') -parser.add_argument('-a', '--all', action='store_true', - default=False, help='show all commits') -parser.add_argument('-sv', '--semver', action='store_true', - default=False, help='relation with latest semver tag') -parser.add_argument('-svn', '--semvern', type=int, - default=0, help='relation with semver tag before n semantic versions') -parser.add_argument('-md', '--markdown', action='store_true', default=False, help='Markdown (and remove merge commits)') -parser.add_argument('-g', '--grep', type=str, default="", help='Filter only commits containing this string') -parser.add_argument('-ig', '--inverted-grep', type=str, default="", help='Filter only commits NOT containing this string') - -args = parser.parse_args() - -branch_1: str = args.branch_1 -branch_2: str = args.branch_2 -branch_2_latest_semver: bool = args.semver -branch_2_n_old_semver: int = args.semvern -only_messages: str = args.msg -markdown = args.markdown -grep_string = args.grep -inverted_grep_string = args.inverted_grep -brief = args.brief - -if branch_2_latest_semver: - branch_2_n_old_semver = 1 -if branch_2_n_old_semver: - versions = semver.get_all_versions_ordered() - if not versions: - print('no semver tags found') - sys.exit(1) - try: - branch_2 = versions[-branch_2_n_old_semver].tag - except: - print(f'too few semver tags for {branch_2_n_old_semver}') - sys.exit(1) -if args.upstream: - if branch_2 and branch_2 != "HEAD": - print(f'cannot use --upstream and specify both branches ({branch_1} and {branch_2})') - sys.exit(1) - success, optput = git.execute_git(f'rev-parse --abbrev-ref {branch_1}@{{u}}', output=False) +def main(): + git.assert_in_git_repository() + + parser = argparse.ArgumentParser( + description='Show a relation between two git commits/tags/branches') + + parser.add_argument('branch_1', metavar='branch_1', type=str, default='HEAD', nargs='?', + help='the one commit/tag/branch') + parser.add_argument('branch_2', metavar='branch_2', type=str, nargs='?', default='HEAD', + help='the other commit/tag/branch (default HEAD)') + parser.add_argument('-b', '--brief', action='store_true', default=False, help='brief output') + parser.add_argument('-m', '--msg', action='store_true', + default=False, help='only commit messages') + parser.add_argument('-u', '--upstream', action='store_true', + default=False, help='relation with upstream branch') + parser.add_argument('-a', '--all', action='store_true', + default=False, help='show all commits') + parser.add_argument('-sv', '--semver', action='store_true', + default=False, help='relation with latest semver tag') + parser.add_argument('-svn', '--semvern', type=int, + default=0, help='relation with semver tag before n semantic versions') + parser.add_argument('-md', '--markdown', action='store_true', default=False, help='Markdown (and remove merge commits)') + parser.add_argument('-g', '--grep', type=str, default="", help='Filter only commits containing this string') + parser.add_argument('-ig', '--inverted-grep', type=str, default="", help='Filter only commits NOT containing this string') + + args = parser.parse_args() + + branch_1: str = args.branch_1 + branch_2: str = args.branch_2 + branch_2_latest_semver: bool = args.semver + branch_2_n_old_semver: int = args.semvern + only_messages: str = args.msg + markdown = args.markdown + grep_string = args.grep + inverted_grep_string = args.inverted_grep + brief = args.brief + + if branch_2_latest_semver: + branch_2_n_old_semver = 1 + if branch_2_n_old_semver: + versions = semver.get_all_versions_ordered() + if not versions: + print('no semver tags found') + sys.exit(1) + try: + branch_2 = versions[-branch_2_n_old_semver].tag + except: + print(f'too few semver tags for {branch_2_n_old_semver}') + sys.exit(1) + + if args.upstream: + if branch_2 and branch_2 != "HEAD": + print(f'cannot use --upstream and specify both branches ({branch_1} and {branch_2})') + sys.exit(1) + success, optput = git.execute_git(f'rev-parse --abbrev-ref {branch_1}@{{u}}', output=False) + if not success: + print('error getting upstream branch') + sys.exit(1) + first_line = optput.split("\n")[0] + branch_2 = first_line + + + def print_log(commit_1: str, commit_2: str, all_commits: bool=False) -> None: + cmd = ['log', f'{commit_1}..{commit_2}'] + if only_messages: + cmd.append('--oneline') + elif markdown: + cmd.append('--format=- %s') + else: + cmd.append('--format=%x20%x20%x20%h%x20%Cgreen%an%Creset%x20\"%Cred%s%Creset\",%x20%ar') + + if grep_string: + cmd.append(f'--grep={grep_string}') + elif inverted_grep_string: + cmd.append(f'--grep={inverted_grep_string}') + cmd.append('--invert-grep') + + success, log = git.execute_git(cmd, output=False) + if not success: + print('Error retrieving log %s..%s' % (commit_1, commit_2)) + sys.exit(1) + result = log.rstrip() + + lines = result.split('\n') + + print() + print('%s commits from \033[1;34m%s\033[0m to \033[1;34m%s\033[0m:' % (len(lines), commit_1, commit_2)) + + if all_commits or len(lines) < 30: + print(result) + return + + first = lines[:10] + last = lines[-10:] + + lines = first + [' ...%s more commits...' % (len(lines) - len(first) - len(last))] + last + + print('\n'.join(lines)) + + branch_1_sha1 = git.get_git_sha1(branch_1) + branch_2_sha1 = git.get_git_sha1(branch_2) + + success, merge_base = git.execute_git('merge-base %s %s' % (branch_1, branch_2), + output=False) + merge_base = merge_base.strip() if not success: - print('error getting upstream branch') + print('Can\'t find merge base for %s and %s' % (branch_1, branch_2)) sys.exit(1) - first_line = optput.split("\n")[0] - branch_2 = first_line - -def print_log(commit_1: str, commit_2: str, all_commits: bool=False) -> None: - cmd = ['log', f'{commit_1}..{commit_2}'] - if only_messages: - cmd.append('--oneline') - elif markdown: - cmd.append('--format=- %s') + elif merge_base == branch_1_sha1 and merge_base == branch_2_sha1: + print('\033[1;34m%s\033[0m \033[1;31mEQUALS\033[0m \033[1;34m%s\033[0m' % (branch_1, branch_2)) + elif merge_base == branch_1_sha1: + print('\033[1;34m%s\033[0m is \033[1;31mBEHIND\033[0m \033[1;34m%s\033[0m by %d commits' % (branch_1, branch_2, git.distance_to_commit(branch_1, branch_2))) + if not brief: + print_log(branch_1, branch_2, args.all) + elif merge_base == branch_2_sha1: + print('\033[1;34m%s\033[0m is \033[1;31mAHEAD\033[0m of \033[1;34m%s\033[0m by %d commits' % (branch_1, branch_2, git.distance_to_commit(branch_2, branch_1))) + if not args.brief: + print_log(branch_2, branch_1, args.all) else: - cmd.append('--format=%x20%x20%x20%h%x20%Cgreen%an%Creset%x20\"%Cred%s%Creset\",%x20%ar') - - if grep_string: - cmd.append(f'--grep={grep_string}') - elif inverted_grep_string: - cmd.append(f'--grep={inverted_grep_string}') - cmd.append('--invert-grep') - - success, log = git.execute_git(cmd, output=False) - if not success: - print('Error retrieving log %s..%s' % (commit_1, commit_2)) - sys.exit(1) - result = log.rstrip() - - lines = result.split('\n') - - print() - print('%s commits from \033[1;34m%s\033[0m to \033[1;34m%s\033[0m:' % (len(lines), commit_1, commit_2)) - - if all_commits or len(lines) < 30: - print(result) - return - - first = lines[:10] - last = lines[-10:] - - lines = first + [' ...%s more commits...' % (len(lines) - len(first) - len(last))] + last - - print('\n'.join(lines)) - -branch_1_sha1 = git.get_git_sha1(branch_1) -branch_2_sha1 = git.get_git_sha1(branch_2) - -success, merge_base = git.execute_git('merge-base %s %s' % (branch_1, branch_2), - output=False) -merge_base = merge_base.strip() -if not success: - print('Can\'t find merge base for %s and %s' % (branch_1, branch_2)) - sys.exit(1) - -elif merge_base == branch_1_sha1 and merge_base == branch_2_sha1: - print('\033[1;34m%s\033[0m \033[1;31mEQUALS\033[0m \033[1;34m%s\033[0m' % (branch_1, branch_2)) -elif merge_base == branch_1_sha1: - print('\033[1;34m%s\033[0m is \033[1;31mBEHIND\033[0m \033[1;34m%s\033[0m by %d commits' % (branch_1, branch_2, git.distance_to_commit(branch_1, branch_2))) - if not brief: - print_log(branch_1, branch_2, args.all) -elif merge_base == branch_2_sha1: - print('\033[1;34m%s\033[0m is \033[1;31mAHEAD\033[0m of \033[1;34m%s\033[0m by %d commits' % (branch_1, branch_2, git.distance_to_commit(branch_2, branch_1))) - if not args.brief: - print_log(branch_2, branch_1, args.all) -else: - print('\033[1;34m%s\033[0m and \033[1;34m%s\033[0m \033[1;31mDIVERGED\033[0m by respectively %d and %d commits' % (branch_1, branch_2, git.distance_to_commit(merge_base, branch_1), git.distance_to_commit(merge_base, branch_2))) - if not args.brief: - print('common point is \033[1;34m%s\033[0m'%(merge_base)) - print_log(merge_base, branch_1, args.all) - print_log(merge_base, branch_2, args.all) + print('\033[1;34m%s\033[0m and \033[1;34m%s\033[0m \033[1;31mDIVERGED\033[0m by respectively %d and %d commits' % (branch_1, branch_2, git.distance_to_commit(merge_base, branch_1), git.distance_to_commit(merge_base, branch_2))) + if not args.brief: + print('common point is \033[1;34m%s\033[0m'%(merge_base)) + print_log(merge_base, branch_1, args.all) + print_log(merge_base, branch_2, args.all) diff --git a/gitplus/cmd_git_semver.py b/gitplus/cmd_git_semver.py index 8be67c6..ca0f15d 100755 --- a/gitplus/cmd_git_semver.py +++ b/gitplus/cmd_git_semver.py @@ -21,56 +21,58 @@ from typing import * -git.assert_in_git_repository() -parser = argparse.ArgumentParser(description='List / update semver tags') +def main(): + git.assert_in_git_repository() -parser.add_argument('--major', action='store_true', default=False, help='Increase major version') -parser.add_argument('--minor', action='store_true', default=False, help='Increase minor version') -parser.add_argument('--patch', action='store_true', default=False, help='Increase patch version') -parser.add_argument('--suffix', type=str, default="", help='Suffix (for example v1.2.3-suffix)') + parser = argparse.ArgumentParser(description='List / update semver tags') -args = parser.parse_args() -incr_major: bool = args.major -incr_minor: bool = args.minor -incr_patch: bool = args.patch -suffix: str = args.suffix + parser.add_argument('--major', action='store_true', default=False, help='Increase major version') + parser.add_argument('--minor', action='store_true', default=False, help='Increase minor version') + parser.add_argument('--patch', action='store_true', default=False, help='Increase patch version') + parser.add_argument('--suffix', type=str, default="", help='Suffix (for example v1.2.3-suffix)') -versions = semver.get_all_versions_ordered(output_non_versions=True) -max_version = versions[-1] if versions else semver.Version("v0.0.0", "v", 0, 0, 0, "") + args = parser.parse_args() + incr_major: bool = args.major + incr_minor: bool = args.minor + incr_patch: bool = args.patch + suffix: str = args.suffix -if not versions: - print("No tags") - sys.exit(0) + versions = semver.get_all_versions_ordered(output_non_versions=True) + max_version = versions[-1] if versions else semver.Version("v0.0.0", "v", 0, 0, 0, "") -if incr_major or incr_minor or incr_patch: - if incr_patch: - max_version.patch += 1 - if incr_minor: - max_version.minor += 1 - max_version.patch = 0 - if incr_major: - max_version.major += 1 - max_version.minor = 0 - max_version.patch = 0 - new_tag = f'{max_version.prefix}{max_version.major}.{max_version.minor}.{max_version.patch}' - if suffix: - new_tag += "-" + suffix - print(f"Creating new version/tag: {new_tag}") - success, output = git.execute_git(f"tag {new_tag}") - if not success: - print(f'Error creating tag: {output}') - sys.exit(1) - print(f'Tag {new_tag} created, you can push it now') -else: - for n, version in enumerate(versions): - if n > 0: - previous_ver = versions[n-1] - if previous_ver.major != version.major: - print(f"New major: {version.major}") - if previous_ver.minor != version.minor: - print(f"New minor: {version.major}.{version.minor}") - print(f" * {version.tag}") - print() - print(f"Last version: {max_version.tag}") - print() \ No newline at end of file + if not versions: + print("No tags") + sys.exit(0) + + if incr_major or incr_minor or incr_patch: + if incr_patch: + max_version.patch += 1 + if incr_minor: + max_version.minor += 1 + max_version.patch = 0 + if incr_major: + max_version.major += 1 + max_version.minor = 0 + max_version.patch = 0 + new_tag = f'{max_version.prefix}{max_version.major}.{max_version.minor}.{max_version.patch}' + if suffix: + new_tag += "-" + suffix + print(f"Creating new version/tag: {new_tag}") + success, output = git.execute_git(f"tag {new_tag}") + if not success: + print(f'Error creating tag: {output}') + sys.exit(1) + print(f'Tag {new_tag} created, you can push it now') + else: + for n, version in enumerate(versions): + if n > 0: + previous_ver = versions[n-1] + if previous_ver.major != version.major: + print(f"New major: {version.major}") + if previous_ver.minor != version.minor: + print(f"New minor: {version.major}.{version.minor}") + print(f" * {version.tag}") + print() + print(f"Last version: {max_version.tag}") + print() \ No newline at end of file diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..ad32a8f --- /dev/null +++ b/poetry.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +package = [] + +[metadata] +lock-version = "2.0" +python-versions = ">=3.10,<3.13" +content-hash = "03d82cb6c329074837eda8efcc945723cb402f62b8aaaa7a028c1d7bb29392b1" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..5dc16af --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,34 @@ +[tool.poetry] +name = "gitplus" +version = "0.4.0" +description = "" +authors = [ + "Tomo Krajina ", + "Isaac Riley " +] +readme = "README.md" +packages = [{ include = "gitplus", from = "." }] + +[tool.poetry.dependencies] +python = ">=3.10,<3.13" + +[tool.poetry.scripts] +git-multi = "gitplus.cmd_git_multi:main" +git-old-branches = "gitplus.cmd_git_old_branches:main" +git-recent = "gitplus.cmd_git_recent:main" +git-relation = "gitplus.cmd_git_relation:main" +git-semver = "gitplus.cmd_git_semver:main" + +[tool.pylint] +max-line-length = 100 +docstring-min-length=4 + +[tool.black] +line-length = 100 + +[tool.isort] +profile = "black" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/setup.py b/setup.py deleted file mode 100755 index 8ba98c4..0000000 --- a/setup.py +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright 2013 Tomo Krajina -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import distutils.core as mod_distutilscore - -mod_distutilscore.setup( - name='git-plus', - version='v0.4.10', - description='Set of git utilities', - license='Apache License, Version 2.0', - author='Tomo Krajina', - author_email='tkrajina@gmail.com', - url='https://github.com/tkrajina/git-plus', - packages=['gitplus', ], - classifiers=[ - "Programming Language :: Python", - "Programming Language :: Python :: 3", - ], - scripts=[ - 'git-multi', - 'git-old-branches', - 'git-recent', - 'git-relation', - 'git-semver', - ] -)