mirror of
https://github.com/newren/git-filter-repo.git
synced 2024-07-06 18:32:14 +02:00
filter-repo: restructure argument parsing for re-use
Signed-off-by: Elijah Newren <newren@gmail.com>
This commit is contained in:
parent
9bb4188e83
commit
2f3a445875
347
git-filter-repo
347
git-filter-repo
@ -25,9 +25,9 @@ import textwrap
|
|||||||
from datetime import tzinfo, timedelta, datetime
|
from datetime import tzinfo, timedelta, datetime
|
||||||
|
|
||||||
__all__ = ["Blob", "Reset", "FileChanges", "Commit", "Tag", "Progress",
|
__all__ = ["Blob", "Reset", "FileChanges", "Commit", "Tag", "Progress",
|
||||||
"Checkpoint", "FastExportFilter", "FixedTimeZone",
|
"Checkpoint", "FastExportFilter", "FixedTimeZone", "ProgressWriter",
|
||||||
"fast_export_output", "fast_import_input", "get_commit_count",
|
"fast_export_output", "fast_import_input", "get_commit_count",
|
||||||
"get_total_objects", "record_id_rename"]
|
"get_total_objects", "record_id_rename", "FilteringOptions"]
|
||||||
|
|
||||||
|
|
||||||
def _timedelta_to_seconds(delta):
|
def _timedelta_to_seconds(delta):
|
||||||
@ -1670,186 +1670,203 @@ _CURRENT_STREAM_NUMBER = 0
|
|||||||
|
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
||||||
class AppendFilter(argparse.Action):
|
class FilteringOptions(object):
|
||||||
def __call__(self, parser, namespace, values, option_string=None):
|
class AppendFilter(argparse.Action):
|
||||||
suffix = option_string[len('--path-'):] or 'match'
|
def __call__(self, parser, namespace, values, option_string=None):
|
||||||
if suffix == 'rename':
|
suffix = option_string[len('--path-'):] or 'match'
|
||||||
mod_type = 'rename'
|
if suffix == 'rename':
|
||||||
match_type = 'prefix'
|
mod_type = 'rename'
|
||||||
elif suffix.startswith('rename-'):
|
match_type = 'prefix'
|
||||||
mod_type = 'rename'
|
elif suffix.startswith('rename-'):
|
||||||
match_type = suffix[len('rename-'):]
|
mod_type = 'rename'
|
||||||
else:
|
match_type = suffix[len('rename-'):]
|
||||||
mod_type = 'filter'
|
else:
|
||||||
match_type = suffix
|
mod_type = 'filter'
|
||||||
items = getattr(namespace, self.dest, []) or []
|
match_type = suffix
|
||||||
items.append((mod_type, match_type, values))
|
items = getattr(namespace, self.dest, []) or []
|
||||||
setattr(namespace, self.dest, items)
|
items.append((mod_type, match_type, values))
|
||||||
|
setattr(namespace, self.dest, items)
|
||||||
|
|
||||||
class HelperFilter(argparse.Action):
|
class HelperFilter(argparse.Action):
|
||||||
def __call__(self, parser, namespace, values, option_string=None):
|
def __call__(self, parser, namespace, values, option_string=None):
|
||||||
af = AppendFilter(dest='path_changes', option_strings=None)
|
af = FilteringOptions.AppendFilter(dest='path_changes',
|
||||||
dirname = values if values[-1] == '/' else values+'/'
|
option_strings=None)
|
||||||
if option_string == '--subdirectory-filter':
|
dirname = values if values[-1] == '/' else values+'/'
|
||||||
af(parser, namespace, dirname, '--path-match')
|
if option_string == '--subdirectory-filter':
|
||||||
af(parser, namespace, dirname+':', '--path-rename')
|
af(parser, namespace, dirname, '--path-match')
|
||||||
elif option_string == '--to-subdirectory-filter':
|
af(parser, namespace, dirname+':', '--path-rename')
|
||||||
af(parser, namespace, ':'+dirname, '--path-rename')
|
elif option_string == '--to-subdirectory-filter':
|
||||||
else:
|
af(parser, namespace, ':'+dirname, '--path-rename')
|
||||||
raise SystemExit("Error: HelperFilter given invalid option_string: {}"
|
else:
|
||||||
.format(option_string))
|
raise SystemExit("Error: HelperFilter given invalid option_string: {}"
|
||||||
|
.format(option_string))
|
||||||
|
|
||||||
def get_args():
|
@staticmethod
|
||||||
# Include usage in the summary, so we can put the description first
|
def create_arg_parser():
|
||||||
summary = '''Rewrite (or analyze) repository history
|
# Include usage in the summary, so we can put the description first
|
||||||
|
summary = '''Rewrite (or analyze) repository history
|
||||||
|
|
||||||
git-filter-repo destructively rewrites history (unless --analyze or --dry-run
|
git-filter-repo destructively rewrites history (unless --analyze or --dry-run
|
||||||
are specified) according to specified rules. It refuses to do any rewriting
|
are specified) according to specified rules. It refuses to do any rewriting
|
||||||
unless either run from a clean fresh clone, or --force was specified.
|
unless either run from a clean fresh clone, or --force was specified.
|
||||||
|
|
||||||
Basic Usage:
|
Basic Usage:
|
||||||
git-filter-repo --analyze
|
git-filter-repo --analyze
|
||||||
git-filter-repo [FILTER/RENAME/CONTROL OPTIONS]
|
git-filter-repo [FILTER/RENAME/CONTROL OPTIONS]
|
||||||
|
|
||||||
See EXAMPLES section for details.
|
See EXAMPLES section for details.
|
||||||
'''.rstrip()
|
'''.rstrip()
|
||||||
|
|
||||||
# Provide a long helpful examples section
|
# Provide a long helpful examples section
|
||||||
example_text = '''EXAMPLES
|
example_text = '''EXAMPLES
|
||||||
|
|
||||||
To get help:
|
To get help:
|
||||||
git-filter-repo --help
|
git-filter-repo --help
|
||||||
'''
|
'''
|
||||||
|
|
||||||
# Create the basic parser
|
# Create the basic parser
|
||||||
parser = argparse.ArgumentParser(description=summary,
|
parser = argparse.ArgumentParser(description=summary,
|
||||||
usage = argparse.SUPPRESS,
|
usage = argparse.SUPPRESS,
|
||||||
add_help = False,
|
add_help = False,
|
||||||
epilog = example_text,
|
epilog = example_text,
|
||||||
formatter_class=argparse.RawDescriptionHelpFormatter)
|
formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||||
# FIXME: Need to special case all --* args that rev-list takes, or call
|
# FIXME: Need to special case all --* args that rev-list takes, or call
|
||||||
# git rev-parse ...
|
# git rev-parse ...
|
||||||
analyze = parser.add_argument_group(title='Analysis')
|
analyze = parser.add_argument_group(title='Analysis')
|
||||||
analyze.add_argument('--analyze', action='store_true',
|
analyze.add_argument('--analyze', action='store_true',
|
||||||
help='''Analyze repository history and create a
|
help='''Analyze repository history and create a
|
||||||
report that may be useful in determining
|
report that may be useful in determining
|
||||||
what to filter in a subsequent run. Will
|
what to filter in a subsequent run. Will
|
||||||
not modify your repo.''')
|
not modify your repo.''')
|
||||||
|
|
||||||
refs = parser.add_argument_group(title='Git References (positional args)')
|
refs = parser.add_argument_group(title='Git References (positional args)')
|
||||||
refs.add_argument('refs', nargs='*',
|
refs.add_argument('refs', nargs='*',
|
||||||
help='''git refs (branches, tags, etc.) to rewrite,
|
help='''git refs (branches, tags, etc.) to rewrite,
|
||||||
e.g. 'master v2.1.0 v0.5.0'. Special rev-list
|
e.g. 'master v2.1.0 v0.5.0'. Special rev-list
|
||||||
options, such as --branches, --tags, --all, --glob,
|
options, such as --branches, --tags, --all, --glob,
|
||||||
or --exclude are allowed. [default: --all]. To
|
or --exclude are allowed. [default: --all]. To
|
||||||
avoid mixing new history with old, any references
|
avoid mixing new history with old, any references
|
||||||
not specified will be deleted.''')
|
not specified will be deleted.''')
|
||||||
|
|
||||||
path = parser.add_argument_group(title='Filtering based on paths')
|
path = parser.add_argument_group(title='Filtering based on paths')
|
||||||
path.add_argument('--invert-paths', action='store_false',
|
path.add_argument('--invert-paths', action='store_false',
|
||||||
dest='inclusive',
|
dest='inclusive',
|
||||||
help='''Invert the selection of files from the specified
|
help='''Invert the selection of files from the specified
|
||||||
--path-{match,glob,regex} options below, i.e. only
|
--path-{match,glob,regex} options below, i.e. only
|
||||||
select files matching none of those options.''')
|
select files matching none of those options.''')
|
||||||
|
|
||||||
path.add_argument('--path-match', '--path', metavar='DIR_OR_FILE',
|
path.add_argument('--path-match', '--path', metavar='DIR_OR_FILE',
|
||||||
action=AppendFilter, dest='path_changes',
|
action=FilteringOptions.AppendFilter, dest='path_changes',
|
||||||
help='''Exact paths (files or directories) to include in
|
help='''Exact paths (files or directories) to include in
|
||||||
filtered history. Multiple --path options can be
|
filtered history. Multiple --path options can be
|
||||||
specified to get a union of paths.''')
|
specified to get a union of paths.''')
|
||||||
path.add_argument('--path-glob', metavar='GLOB',
|
path.add_argument('--path-glob', metavar='GLOB',
|
||||||
action=AppendFilter, dest='path_changes',
|
action=FilteringOptions.AppendFilter, dest='path_changes',
|
||||||
help='''Glob of paths to include in filtered history.
|
help='''Glob of paths to include in filtered history.
|
||||||
Multiple --path-glob options can be specified to
|
Multiple --path-glob options can be specified to
|
||||||
get a union of paths.''')
|
get a union of paths.''')
|
||||||
path.add_argument('--path-regex', metavar='REGEX',
|
path.add_argument('--path-regex', metavar='REGEX',
|
||||||
action=AppendFilter, dest='path_changes',
|
action=FilteringOptions.AppendFilter, dest='path_changes',
|
||||||
help='''Regex of paths to include in filtered history.
|
help='''Regex of paths to include in filtered history.
|
||||||
Multiple --path-regex options can be specified to
|
Multiple --path-regex options can be specified to
|
||||||
get a union of paths''')
|
get a union of paths''')
|
||||||
|
|
||||||
rename = parser.add_argument_group(title='Renaming based on paths')
|
rename = parser.add_argument_group(title='Renaming based on paths')
|
||||||
rename.add_argument('--path-rename', '--path-rename-prefix',
|
rename.add_argument('--path-rename', '--path-rename-prefix',
|
||||||
metavar='OLD_NAME:NEW_NAME',
|
metavar='OLD_NAME:NEW_NAME',
|
||||||
action=AppendFilter, dest='path_changes',
|
action=FilteringOptions.AppendFilter,
|
||||||
help='''Prefix to rename; if filename starts with
|
dest='path_changes',
|
||||||
OLD_NAME, replace that with NEW_NAME. Multiple
|
help='''Prefix to rename; if filename starts with
|
||||||
--path-rename options can be specified.''')
|
OLD_NAME, replace that with NEW_NAME. Multiple
|
||||||
|
--path-rename options can be specified.''')
|
||||||
|
|
||||||
refrename = parser.add_argument_group(title='Renaming of refs')
|
refrename = parser.add_argument_group(title='Renaming of refs')
|
||||||
refrename.add_argument('--tag-rename', metavar='OLD:NEW',
|
refrename.add_argument('--tag-rename', metavar='OLD:NEW',
|
||||||
help='''Rename tags starting with OLD to start with
|
help='''Rename tags starting with OLD to start with
|
||||||
NEW. e.g. --tag-rename foo:bar will rename
|
NEW. e.g. --tag-rename foo:bar will rename
|
||||||
tag foo-1.2.3 to bar-1.2.3; either OLD or NEW
|
tag foo-1.2.3 to bar-1.2.3; either OLD or NEW
|
||||||
can be empty.''')
|
can be empty.''')
|
||||||
|
|
||||||
helpers = parser.add_argument_group(title='Shortcuts')
|
helpers = parser.add_argument_group(title='Shortcuts')
|
||||||
helpers.add_argument('--subdirectory-filter', metavar='DIRECTORY',
|
helpers.add_argument('--subdirectory-filter', metavar='DIRECTORY',
|
||||||
action=HelperFilter,
|
action=FilteringOptions.HelperFilter,
|
||||||
help='''Only look at history that touches the given
|
help='''Only look at history that touches the given
|
||||||
subdirectory and treat that directory as the
|
subdirectory and treat that directory as the
|
||||||
project root. Equivalent to using
|
project root. Equivalent to using
|
||||||
"--path DIRECTORY/ --path-rename DIRECTORY/:"''')
|
"--path DIRECTORY/ --path-rename DIRECTORY/:"
|
||||||
helpers.add_argument('--to-subdirectory-filter', metavar='DIRECTORY',
|
''')
|
||||||
action=HelperFilter,
|
helpers.add_argument('--to-subdirectory-filter', metavar='DIRECTORY',
|
||||||
help='''Treat the project root as instead being under
|
action=FilteringOptions.HelperFilter,
|
||||||
DIRECTORY. Equivalent to using
|
help='''Treat the project root as instead being under
|
||||||
"--path-rename :DIRECTORY/"''')
|
DIRECTORY. Equivalent to using
|
||||||
|
"--path-rename :DIRECTORY/"''')
|
||||||
|
|
||||||
misc = parser.add_argument_group(title='Miscellaneous options')
|
misc = parser.add_argument_group(title='Miscellaneous options')
|
||||||
misc.add_argument('--help', '-h', action='store_true',
|
misc.add_argument('--help', '-h', action='store_true',
|
||||||
help='''Show this help message and exit.''')
|
help='''Show this help message and exit.''')
|
||||||
misc.add_argument('--force', '-f', action='store_true',
|
misc.add_argument('--force', '-f', action='store_true',
|
||||||
help='''Rewrite history even if the current repo does not
|
help='''Rewrite history even if the current repo does not
|
||||||
look like a fresh clone.''')
|
look like a fresh clone.''')
|
||||||
|
|
||||||
misc.add_argument('--dry-run', action='store_true',
|
misc.add_argument('--dry-run', action='store_true',
|
||||||
help='''Do not change the repository. Run `git
|
help='''Do not change the repository. Run `git
|
||||||
fast-export` and filter its output, and save both
|
fast-export` and filter its output, and save both
|
||||||
the original and the filtered version for
|
the original and the filtered version for
|
||||||
comparison. Some filtering of empty commits may
|
comparison. Some filtering of empty commits may
|
||||||
not occur due to inability to query the fast-import
|
not occur due to inability to query the fast-import
|
||||||
backend.''')
|
backend.''')
|
||||||
misc.add_argument('--debug', action='store_true',
|
misc.add_argument('--debug', action='store_true',
|
||||||
help='''Print additional information about operations being
|
help='''Print additional information about operations being
|
||||||
performed and commands being run. When used
|
performed and commands being run. When used
|
||||||
together with --dry-run, also show extra
|
together with --dry-run, also show extra
|
||||||
information about what would be run.''')
|
information about what would be run.''')
|
||||||
misc.add_argument('--stdin', action='store_true',
|
misc.add_argument('--stdin', action='store_true',
|
||||||
help='''Instead of running `git fast-export` and filtering
|
help='''Instead of running `git fast-export` and filtering
|
||||||
its output, filter the fast-export stream from
|
its output, filter the fast-export stream from
|
||||||
stdin.''')
|
stdin.''')
|
||||||
misc.add_argument('--quiet', action='store_true',
|
misc.add_argument('--quiet', action='store_true',
|
||||||
help='''Pass --quiet to other git commands called''')
|
help='''Pass --quiet to other git commands called''')
|
||||||
|
return parser
|
||||||
|
|
||||||
if len(sys.argv) == 1:
|
@staticmethod
|
||||||
parser.print_usage()
|
def sanity_check_args(args):
|
||||||
raise SystemExit("No arguments specified.")
|
if not args.refs:
|
||||||
args = parser.parse_args()
|
args.refs = ['--all']
|
||||||
if args.help:
|
if args.analyze and args.path_changes:
|
||||||
parser.print_help()
|
raise SystemExit("Error: --analyze is incompatible with --path* flags; "
|
||||||
raise SystemExit()
|
"it's a read-only operation.")
|
||||||
if not args.refs:
|
if args.analyze and args.stdin:
|
||||||
args.refs = ['--all']
|
raise SystemExit("Error: --analyze is incompatible with --stdin.")
|
||||||
if args.analyze and args.path_changes:
|
# If no path_changes are found, initialize with empty list but mark as
|
||||||
raise SystemExit("Error: --analyze is incompatible with --path* flags; "
|
# not inclusive so that all files match
|
||||||
"it's a read-only operation.")
|
if args.path_changes == None:
|
||||||
if args.analyze and args.stdin:
|
args.path_changes = []
|
||||||
raise SystemExit("Error: --analyze is incompatible with --stdin.")
|
|
||||||
# If no path_changes are found, initialize with empty list but mark as
|
|
||||||
# not inclusive so that all files match
|
|
||||||
if args.path_changes == None:
|
|
||||||
args.path_changes = []
|
|
||||||
args.inclusive = False
|
|
||||||
# Similarly, if we only have renames, all paths should match
|
|
||||||
else:
|
|
||||||
has_filter = False
|
|
||||||
for (mod_type, match_type, path_expression) in args.path_changes:
|
|
||||||
if mod_type == 'filter':
|
|
||||||
has_filter = True
|
|
||||||
if not has_filter:
|
|
||||||
args.inclusive = False
|
args.inclusive = False
|
||||||
return args
|
# Similarly, if we only have renames, all paths should match
|
||||||
|
else:
|
||||||
|
has_filter = False
|
||||||
|
for (mod_type, match_type, path_expression) in args.path_changes:
|
||||||
|
if mod_type == 'filter':
|
||||||
|
has_filter = True
|
||||||
|
if not has_filter:
|
||||||
|
args.inclusive = False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def default_options():
|
||||||
|
return FilteringOptions.parse_args([], error_on_empty = False)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def parse_args(input_args, error_on_empty = True):
|
||||||
|
parser = FilteringOptions.create_arg_parser()
|
||||||
|
if not input_args and error_on_empty:
|
||||||
|
parser.print_usage()
|
||||||
|
raise SystemExit("No arguments specified.")
|
||||||
|
args = parser.parse_args(input_args)
|
||||||
|
if args.help:
|
||||||
|
parser.print_help()
|
||||||
|
raise SystemExit()
|
||||||
|
FilteringOptions.sanity_check_args(args)
|
||||||
|
return args
|
||||||
|
|
||||||
def is_repository_bare():
|
def is_repository_bare():
|
||||||
output = subprocess.check_output('git rev-parse --is-bare-repository'.split())
|
output = subprocess.check_output('git rev-parse --is-bare-repository'.split())
|
||||||
@ -2474,7 +2491,7 @@ class DualFileWriter:
|
|||||||
self.file2.close()
|
self.file2.close()
|
||||||
|
|
||||||
def run_fast_filter():
|
def run_fast_filter():
|
||||||
args = get_args()
|
args = FilteringOptions.parse_args(sys.argv[1:])
|
||||||
if args.debug:
|
if args.debug:
|
||||||
print("[DEBUG] Parsed arguments:\n{}".format(args))
|
print("[DEBUG] Parsed arguments:\n{}".format(args))
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user