#!/usr/bin/env python3 import argparse import os import os.path as op import shutil import subprocess import sys ABORT_STRING = "Not a known VCS repository." NOTHING_TODO_STRING = "There's nothing else to do." NOTHING_TODO_2_STRING = "Every me and every you." ARGPARSE_DESCRIPTION = "Compares a VCS repository with a local directory, outputs modified files." GIT_COMMAND = ['git', 'ls-files', '--full-name'] GIT_DIRECTORY = '.git' GIT_IGNORE = '.gitignore' HG_COMMAND = ['hg', 'locate'] HG_DIRECTORY = '.hg' HG_IGNORE = '.hgignore' IGNORE_FILES = {'dotfiles': ['home/wasa/.config/llpp.conf', 'home/wasa/.config/mcomix/preferences.conf', 'home/wasa/.mutt/aliases'], 'dotemacs': ['etc/private.el', 'etc/custom.el']} def main(): parser = argparse.ArgumentParser(description=ARGPARSE_DESCRIPTION) subparsers = parser.add_subparsers() parser_diff = subparsers.add_parser('diff', help="Print different files") parser_diff.add_argument('repository', help="VCS repository") parser_diff.add_argument('directory', help="Local directory") parser_diff.set_defaults(call=do_diff) parser_copy = subparsers.add_parser('copy', help="Copy different files") parser_copy.add_argument('repository', help="VCS repository") parser_copy.add_argument('directory', help="Local directory") parser_copy.set_defaults(call=do_copy) args = parser.parse_args() args.call(args) def do_diff(args): vcs_action(args.repository, args.directory, 'diff') def do_copy(args): vcs_action(args.repository, args.directory, 'copy') def vcs_action(repos, directory, action): repos = op.expanduser(repos) repos_type = vcs_type(repos) if repos_type == 'git': repos_files = git_files(repos) elif repos_type == 'hg': repos_files = hg_files(repos) else: sys.exit(ABORT_STRING) repos_basename = op.basename(op.abspath(repos)) ignored_files = [] if repos_basename in IGNORE_FILES: ignored_files = IGNORE_FILES[repos_basename] interesting_files = [] for path in repos_files: remote, local = [op.expanduser(op.join(op.abspath(d), path)) for d in [repos, directory]] if op.exists(local) and op.isfile(local): remote_hash, local_hash = [md5_sum(f) for f in [remote, local]] if remote_hash != local_hash and path not in ignored_files: interesting_files.append({'path': path, 'remote': remote, 'local': local}) if action == 'diff': paths = [f['path'] for f in interesting_files] if paths: print('\n'.join(paths)) else: print(NOTHING_TODO_STRING) elif action == 'copy': if interesting_files: for f in interesting_files: shutil.copy(f['local'], f['remote']) else: print(NOTHING_TODO_2_STRING) def vcs_type(repos): if op.isdir(repos): if op.exists(op.join(repos, GIT_DIRECTORY)): return 'git' elif op.exists(op.join(repos, HG_DIRECTORY)): return 'hg' else: return 'directory' elif op.isfile(repos): return 'file' def git_files(repos): return vcs_files(repos, GIT_COMMAND, GIT_IGNORE) def hg_files(repos): return vcs_files(repos, HG_COMMAND, HG_IGNORE) def vcs_files(repos, command, ignore_file): original_directory = os.getcwd() os.chdir(repos) relative_paths = command_output(command) os.chdir(original_directory) return [path for path in relative_paths if path != ignore_file] def md5_sum(path): return command_output(['md5sum', op.expanduser(path)])[0].split()[0] def command_output(command): return subprocess.check_output(command, universal_newlines=True).split('\n') if __name__ == '__main__': main()