diff --git a/PyGit.egg-info/PKG-INFO b/PyGit.egg-info/PKG-INFO new file mode 100644 index 0000000..d4469c4 --- /dev/null +++ b/PyGit.egg-info/PKG-INFO @@ -0,0 +1,178 @@ +Metadata-Version: 1.1 +Name: PyGit +Version: 3.0 +Summary: Automate common git tasks +Home-page: UNKNOWN +Author: Chidi Orji +Author-email: orjichidi95@gmail.com +License: MIT +Description: # PyGit + ============ + + Automate the boring git stuff with python + + ## Motivation + + Whenever I wanted to see the status of all my git repos I have to fire up the + `git-cmd.exe` shell on windows, navigate to each folder and then do a `git status`. + I have to do this both at home and at work. + + But I got quickly tired of it. So I decided to make this tool to give me a quick + report so I can see what is ahead and what's behind and what's ahead at a glance. + In short, what needs attention so as to avoid those troubling merge conflicts. + + ## Requirements + + The only requirements in terms of software is `send2trash` which helps take care of cleaning up stuff. + Other thing you need is a computer with `git` accessible from the command line. If you're on a computer + where you don't have permission to install stuff, you can do with a portable `git` and `pygit` will work just fine. + + You can get a portable git version from [here](https://git-scm.com/download/win) + + Just unzip it and place it somewhere on your disk. You'll need to tell `pygit` where this file is during intitialization. + + + ## Installation + + Github, `pip install https://github.com/immensity/pygit/archive/3.0.tar.gz` or PyPI `pip install pygit --upgrade` + + ## Usage + + Upon successful installation do + + import pygit + + A successful import returns a blank screen. + + To use `pygit`, you have to tell it where your `git` repositories are. You may do this by passing it a list of strings on the command line. + Each string represents a full path name to single directory. + + If you have a master directory that holds multiple `git` repositories, `pygit` can also take the full path name of this master directory + and then index the git repositories it finds there. It won't index those directories that are not git repos. + + It is also possible to tell `pygit` not to index certain directories by specifying the starting string of the directory name. This is referred + to s a `rule`. Directories matching such rules will not be touched. + + + + + To initialize `pygit`, run + + pygit.initialize() + + In case things change (perhaps you moved folders around) and you want to reset your folders, + just run the `set_all()` command again + + + To see all available repositories run :: + + pygit.show_repos() + + This command shows a list of all available repositories in the format :: + + repository_id: repository_name: repository_directory_path + + To load a particular repository use :: + + pygit.load(repo_id_or_name) + + where **repo_id** is a string-valued id assigned to that particular repo. The first value in the `show_repos` command's output. + + + The **load\(\)** command returns a `Commands` object for that repo, which provides a gateway for issuing git commands on the repository + + Operations that can be performed on `Commands` object are shown below. :: + + Commands().fetch() # perform fetch. + + Commands().status() # see status + + Commands().add_all() # stage all changes for commit + + Commands().commit(message='minor changes') # commit changes. Press enter to accept default message + + Commands().push() # perform push action + + Commands().pull() # perform pull request + + Commands().add_commit() # add and commit at once + + + + ### Batch Operations + + Pygit provides some functions for performing batch operations on your repositories. :: + + pygit.load_multiple(*args) + + loads a set of repositories. You could decide to only load only 2 of 10 repositories. Perhaps you need to perform similar actions + on those two alone. As an example + + pygit.load_set("2", "5") + + returns a `generator` of `Commands` objects for repositories 2 and 5. Afterwards you can use a :py:func:`for` loop to iterate over the repos + like below + + for each in pygit.load_set("2", "5"): + each.add_commit() + + pygit.all_status() + + + performs a **status** command on all your repositories. The result is written to a text file. The text file opens automatically. + The name of the file shows the date and time of the status request. All batch status request is written to its a separate file. + Call it a snapshot of your repo status if you will + Those items which are out of sync with their remote counterpart (by being ahead or being behind) are also highlighted as needing attention. :: + + pygit.pull_all() + + + performs a **pull** request on all your repositories at once. Its `return` value is None. :: + + pygit.push_all() + + + performs a **push** action on all your repositories at once. Its `return` value is None. :: + + pygit.load_all() + + + returns a `generator` of `Commands` object for every repository. + + + API + ===== + .. automodule :: pygit.api + :members: + + Commands + ============= + + .. automodule :: pygit.commands + :members: + + Utility Functions + ======================= + + .. automodule :: pygit.utils + :members: + + To do + ====== + + Add **git-bash.exe** + + Implement Commands.branch() + + Find out why importing pygit for first time gives an PermissionError + Write tests + + Run test after importation to make sure every other thing works fine. + + Define an update function that updates the repo dictionaries for the case when a new repo is added but the overall directory structure remains unchanged. + +Keywords: git and github task automation +Platform: UNKNOWN +Classifier: Developement Status :: 3 - Alpha +Classifier: Programming Language :: Python :: 3.6.1 +Classifier: Topic :: Git :: Automation diff --git a/PyGit.egg-info/SOURCES.txt b/PyGit.egg-info/SOURCES.txt new file mode 100644 index 0000000..e4f0614 --- /dev/null +++ b/PyGit.egg-info/SOURCES.txt @@ -0,0 +1,16 @@ +MANIFEST.in +README.rst +license.txt +setup.py +PyGit.egg-info/PKG-INFO +PyGit.egg-info/SOURCES.txt +PyGit.egg-info/dependency_links.txt +PyGit.egg-info/not-zip-safe +PyGit.egg-info/requires.txt +PyGit.egg-info/top_level.txt +pygit/__init__.py +pygit/binn.py +pygit/main.py +pygit/new.py +pygit/select_directory.py +pygit/test.py \ No newline at end of file diff --git a/PyGit.egg-info/dependency_links.txt b/PyGit.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/PyGit.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/PyGit.egg-info/not-zip-safe b/PyGit.egg-info/not-zip-safe new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/PyGit.egg-info/not-zip-safe @@ -0,0 +1 @@ + diff --git a/PyGit.egg-info/requires.txt b/PyGit.egg-info/requires.txt new file mode 100644 index 0000000..cdb6763 --- /dev/null +++ b/PyGit.egg-info/requires.txt @@ -0,0 +1 @@ +send2trash diff --git a/PyGit.egg-info/top_level.txt b/PyGit.egg-info/top_level.txt new file mode 100644 index 0000000..1e54e9c --- /dev/null +++ b/PyGit.egg-info/top_level.txt @@ -0,0 +1 @@ +pygit diff --git a/UNKNOWN.egg-info/PKG-INFO b/UNKNOWN.egg-info/PKG-INFO new file mode 100644 index 0000000..12a6a75 --- /dev/null +++ b/UNKNOWN.egg-info/PKG-INFO @@ -0,0 +1,8 @@ +Metadata-Version: 1.0 +Name: UNKNOWN +Version: 0.0.0 +Summary: UNKNOWN +Home-page: UNKNOWN +License: UNKNOWN +Description: UNKNOWN +Platform: UNKNOWN diff --git a/UNKNOWN.egg-info/SOURCES.txt b/UNKNOWN.egg-info/SOURCES.txt new file mode 100644 index 0000000..572b54e --- /dev/null +++ b/UNKNOWN.egg-info/SOURCES.txt @@ -0,0 +1,8 @@ +MANIFEST.in +README.rst +license.txt +setup.py +UNKNOWN.egg-info/PKG-INFO +UNKNOWN.egg-info/SOURCES.txt +UNKNOWN.egg-info/dependency_links.txt +UNKNOWN.egg-info/top_level.txt \ No newline at end of file diff --git a/UNKNOWN.egg-info/dependency_links.txt b/UNKNOWN.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/UNKNOWN.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/UNKNOWN.egg-info/top_level.txt b/UNKNOWN.egg-info/top_level.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/UNKNOWN.egg-info/top_level.txt @@ -0,0 +1 @@ + diff --git a/build/lib/pygit/__init__.py b/build/lib/pygit/__init__.py new file mode 100644 index 0000000..d9ebd31 --- /dev/null +++ b/build/lib/pygit/__init__.py @@ -0,0 +1,9 @@ +"""Docstring""" + +from .main import ( + cleanup, check_git_support, is_git_repo, need_attention, initialize, + Commands, show_repos, load, load_multiple, pull, push, all_status +) + +__all__ = ["all_status", "cleanup", "check_git_support", "is_git_repo", "need_attention", "initialize", + "Commands", "show_repos", "load", "load_multiple", "pull", "push", "all_status"] diff --git a/build/lib/pygit/binn.py b/build/lib/pygit/binn.py new file mode 100644 index 0000000..275a755 --- /dev/null +++ b/build/lib/pygit/binn.py @@ -0,0 +1,45 @@ +"""Graphical directory selection""" + +try: + import tkinter as tk + from tkinter import filedialog +except ImportError: + print("tkinter was not found.") + pass + + +def select_directory(title="Select directory"): + """Select and return a directory path""" + try: + root = tk.Tk() + root.withdraw() + initialdir = "/" + root_dir = filedialog.askdirectory(parent=root, + initialdir=initialdir, + title=title) + except: + print("Tk inter was not found.") + return root_dir + +import os, sys, site + +# https://stackoverflow.com/questions/36187264/how-to-get-installation-directory-using-setuptools-and-pkg-ressources +def binaries_directory(): + """Return the installation directory, or None""" + if '--user' in sys.argv: + paths = (site.getusersitepackages(),) + else: + py_version = '%s.%s' % (sys.version_info[0], sys.version_info[1]) + paths = (s % (py_version) for s in ( + sys.prefix + '/lib/python%s/dist-packages/', + sys.prefix + '/lib/python%s/site-packages/', + sys.prefix + '/local/lib/python%s/dist-packages/', + sys.prefix + '/local/lib/python%s/site-packages/', + '/Library/Python/%s/site-packages/', + )) + + for path in paths: + if os.path.exists(path): + return path + print('no installation path found', file=sys.stderr) + return None diff --git a/build/lib/pygit/main.py b/build/lib/pygit/main.py new file mode 100644 index 0000000..ff329b3 --- /dev/null +++ b/build/lib/pygit/main.py @@ -0,0 +1,471 @@ +"""pygit""" + +# https://stackoverflow.com/questions/19687394/python-script-to-determine-if-a-directory-is-a-git-repository +# http://gitpython.readthedocs.io/en/stable/ + +import os +import sys +import shutil +import shelve +import argparse +from datetime import datetime +from subprocess import Popen, PIPE, STDOUT + +from send2trash import send2trash + +USERHOME = os.path.abspath(os.path.expanduser('~')) +DESKTOP = os.path.abspath(USERHOME + '/Desktop/') +TIMING = datetime.now().strftime("%a_%d_%b_%Y_%H_%M_%S_%p") + +STATUS_DIR = os.path.join(DESKTOP ,"status") +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +SHELF_DIR = os.path.join(BASE_DIR, "shelves") +TEST_DIR = os.path.join(BASE_DIR, "test_git/") + +def clear_screen(): + if sys.platform == 'win32': + os.system('cls') + if sys.platform == 'linux': + os.system('clear') + +def cleanup(): + """Cleanup files""" + send2trash(SHELF_DIR) + +# keep for later +def kill_process(process): + if process.poll() is None: # don't send the signal unless it seems it is necessary + try: + process.kill() + except PermissionError: # ignore + print("Os error. cannot kill kill_process") + pass + +def check_git_support(): + """Check if a git repo can be initialized in present shell""" + try: + os.mkdir(TEST_DIR) + except FileExistsError: + shutil.rmtree(TEST_DIR) + os.mkdir(TEST_DIR) + + os.chdir(TEST_DIR) + process = Popen("git init", shell=True, stdin=PIPE, stdout=PIPE, stderr=STDOUT,) + output, _ = process.communicate() + msg = str(output.decode("utf-8")) + # print(msg) + if "Initialized empty Git repository" in msg: + os.chdir(BASE_DIR) + shutil.rmtree(TEST_DIR) + return True + return False + +def is_git_repo(directory): + """Determine if a folder is a git repo + Checks for the presence of a .git folder inside a directory + """ + if ".git" in os.listdir(directory): + return True + return False + +def need_attention(status): + """Return True if a repo status is not same as that of remote""" + msg = ["staged", "behind", "ahead", "Untracked"] + if any([each in status for each in msg]): + return True + return False + +def initialize(): + clear_screen() + + """Initialize the data necessary for pygit to operate + + Parameters + ----------- + gitPath : str + Optional, Path to a git executable, if git is not in your system path. + masterDirectory : str + Optional, Full pathname to directory with multiple git repos + simpleDirectory : list + A list of full pathname to individual git repos. + + Notes + ------ + Accepts command line inputs only. + """ + try: + os.mkdir(SHELF_DIR) + except FileExistsError: + shutil.rmtree(SHELF_DIR) + os.mkdir(SHELF_DIR) + + name_shelf = shelve.open(os.path.join(SHELF_DIR, "name_shelf")) + index_shelf = shelve.open(os.path.join(SHELF_DIR, "index_shelf")) + + parser = argparse.ArgumentParser(prog="Pygit. Initialize pygit's working directories.") + parser.add_argument("-v", "--verbosity", type=int, help="turn verbosity ON/OFF", choices=[0,1]) + parser.add_argument("-r", "--rules", help="Set a list of string patterns for folders you don't want pygit to touch", nargs='+') + parser.add_argument('-g', '--gitPath', help="Full pathname to git executable. cmd or bash.") + parser.add_argument('-m', '--masterDirectory', help="Full pathname to directory with multiple git repos.") + parser.add_argument('-s', '--simpleDirectory', help="A list of full pathname to individual git repos.", nargs='+') + parser.add_argument('-t', '--statusDirectory', help="Full pathname to directory for writing status.") # make mandatory + + args = parser.parse_args() + + if args.gitPath: + for _, __, files in os.walk(args.gitPath): + if "git-cmd.exe" in files: + name_shelf['GIT_WINDOWS'] = args.gitPath + elif "git-bash.exe" in files: + name_shelf['GIT_BASH'] = args.gitPath + else: + print("A valid git executable was not found in the directory.\n") + pass + + else: + if check_git_support(): + if args.verbosity: + print("Your system supports git out of the box.\n") + elif "git" in os.environ['PATH']: + user_paths = os.environ['PATH'].split(os.pathsep) + for path in user_paths: + if "git-cmd.exe" in path: + name_shelf['GIT_WINDOWS'] = path + break + if "git-bash.exe" in path: + name_shelf['GIT_BASH'] = path + break + else: + print("Git was not found in your system path.\nYou may need to set the location manually using the -g flag.\n") + + if args.masterDirectory: + + if args.verbosity: + print("Master directory set to ", args.masterDirectory, "\n") + print("Now working on folders ... Please wait a few minutes.\n") + + i = len(list(index_shelf.keys())) + 1 + for path, _, __ in os.walk(args.masterDirectory): + if path.startswith("."): + continue + if args.rules: + # if any of the string patterns is found in path name, that folder will be skipped. + if any([rule in path for rule in args.rules]): + if args.verbosity: + print(path, " matches a rule. Skipping") + continue + + directory_absolute_path = os.path.abspath(path) + + if is_git_repo(directory_absolute_path): + if sys.platform == 'win32': + name = directory_absolute_path.split("\\")[-1] + if sys.platform == 'linux': + name = directory_absolute_path.split("/")[-1] + + name_shelf[name] = directory_absolute_path + index_shelf[str(i)] = name + i += 1 + name_shelf.close() + index_shelf.close() + + if args.simpleDirectory: + if args.verbosity: + print("Now shelving the following directories\n") + print(args.simpleDirectory) + + i = len(list(index_shelf.keys())) + 1 + for directory in args.simpleDirectory: + + if is_git_repo(directory): + if sys.platform == 'win32': + name = directory.split("\\")[-1] + if sys.platform == 'linux': + name = directory.split("/")[-1] + + name_shelf[name] = directory + index_shelf[str(i)] = name + else: + print(directory, " is not a valid git repo.\nContinuing...") + continue + i += 1 + + name_shelf.close() + index_shelf.close() + + global STATUS_DIR + if args.statusDirectory: + STATUS_DIR = args.statusDirectory + if args.verbosity: + print("\nStatus files to be saved in {}\n".format(STATUS_DIR)) + else: + if args.verbosity: + print("\nStatus files to be saved in {}\n".format(STATUS_DIR)) + + if args.verbosity: + print("\nDone.\nThe following directories were set.\n") + name_shelf = shelve.open(os.path.join(SHELF_DIR, "name_shelf")) + index_shelf = shelve.open(os.path.join(SHELF_DIR, "index_shelf")) + + print("{:<4} {:<20} {:<}".format("Key", "| Name", "| Path")) + print("****************************") + for key in index_shelf.keys(): + name = index_shelf[key] + print("{:<4} {:<20} {:<}".format(key, name, name_shelf[name])) + index_shelf.close() + name_shelf.close() + return + +class Commands: + """Commands class + + Parameters + ----------- + repo_name : str + The repository name. See list of repositories by running + master_directory : str + The absolute path to the directory + git_exec : str + The path to the git executable on the system + message : str + Commit message + + Returns + -------- + : Commands object + """ + + def __str__(self): + return "Commands: {}: {}".format(self.name, self.dir) + + def __eq__(self, other): + if isinstance(other, self.__class__): + return self.__dict__ == other.__dict__ + else: + return False + + def __init__(self, repo_name, master_directory, git_exec=None, message="minor changes"): + self.name = repo_name + self.dir = master_directory + self.git_exec = git_exec + self.message = message + + try: + os.chdir(self.dir) + except (FileNotFoundError, TypeError): + print("{} may have been moved.\n Run initialize() to update paths".format(self.name)) + self.dir = os.getcwd() + + def fetch(self): + """git fetch""" + if self.git_exec: + process = Popen([self.git_exec, "git fetch"], stdin=PIPE, stdout=PIPE, stderr=STDOUT) + else: + process = Popen("git fetch", shell=True, stdin=PIPE, stdout=PIPE, stderr=STDOUT) + # output, error = process.communicate() + process.communicate() + + def status(self): + """git status""" + self.fetch() # always do a fetch before reporting status + if self.git_exec: + process = Popen([self.git_exec, "git status"], stdin=PIPE, stdout=PIPE, stderr=STDOUT) + else: + process = Popen("git status", shell=True, stdin=PIPE, stdout=PIPE, stderr=STDOUT) + output, _ = process.communicate() + return str(output.decode("utf-8")) + + def stage_file(self, file_name): + """git add file""" + stage_file = 'git add {}'.format(file_name) + if self.git_exec: + process = Popen([self.git_exec, stage_file], stdin=PIPE, stdout=PIPE, stderr=STDOUT,) + else: + process = Popen(stage_file, shell=True, stdin=PIPE, stdout=PIPE, stderr=STDOUT,) + + output, _ = process.communicate() + return str(output.decode("utf-8")) + + def stage_all(self, files="."): + """git add all""" + files = "` ".join(files.split()) + stage_file = 'git add {}'.format(files) + if self.git_exec: + process = Popen([self.git_exec, stage_file], stdin=PIPE, stdout=PIPE, stderr=STDOUT,) + else: + process = Popen(stage_file, shell=True, stdin=PIPE, stdout=PIPE, stderr=STDOUT,) + + process = Popen([self.git_exec, stage_file], stdin=PIPE, stdout=PIPE, stderr=STDOUT,) + output, _ = process.communicate() + return str(output.decode("utf-8")) + + def commit(self): + """git commit""" + enter = input("Commit message.\nPress enter to use 'minor changes'") + if enter == "": + message = self.message + else: + message = enter + # message = "` ".join(message.split()) + if self.git_exec: + process = Popen([self.git_exec, 'git', 'commit', '-m', message], stdin=PIPE, stdout=PIPE, stderr=PIPE,) + else: + process = Popen(['git', 'commit', '-m', message], shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE,) + output, _ = process.communicate() + return str(output.decode("utf-8")) + + def stage_and_commit(self): + """git add followed by commit""" + self.stage_all() + self.commit() + + def push(self): + """git push""" + process = Popen([self.git_exec, 'git push'], stdin=PIPE, stdout=PIPE, stderr=STDOUT,) + output, _ = process.communicate() + return str("Push completed.{}".format(str(output.decode("utf-8")))) + + def pull(self): + """git pull""" + process = Popen([self.git_exec, 'git pull'], stdin=PIPE, stdout=PIPE, stderr=STDOUT,) + output, _ = process.communicate() + return str("Pull completed.\n{}".format(str(output.decode("utf-8")))) + + def reset(self, number='1'): + """git reset""" + process = Popen([self.git_exec, 'git reset HEAD~', number], stdin=PIPE, stdout=PIPE, stderr=STDOUT,) + output, _ = process.communicate() + return str(output.decode("utf-8")) + + # def branch(self): + # """Return the branch being tracked by local""" + # process = Popen([self.git_exec, 'git branch -vv'], shell=True, + # stdin=PIPE, stdout=PIPE, stderr=STDOUT,) + # output, _ = process.communicate() + # out_text = str(output.decode("utf-8")) + # try: + # line = [each for each in out_text.split("\n") if each.startswith("*")][0] + # except IndexError: # no lines start with * + # return + # branch_name = re.search(r"\[origin\/(.*)\]", line) + # return branch_name.group(1) + +def show_repos(): + """Show all available repositories, path, and unique ID""" + clear_screen() + print("\nThe following repos are available.\n") + name_shelf = shelve.open(os.path.join(SHELF_DIR, "name_shelf")) + index_shelf = shelve.open(os.path.join(SHELF_DIR, "index_shelf")) + + print("{:<4} {:<20} {:<}".format("Key", "| Name", "| Path")) + print("******************************************") + for key in index_shelf.keys(): + name = index_shelf[key] + print("{:<4} {:<20} {:<}".format(key, name, name_shelf[name])) + index_shelf.close() + name_shelf.close() + +def load(input_string): # id is string + """Load a repository with specified id""" + name_shelf = shelve.open(os.path.join(SHELF_DIR, "name_shelf")) + index_shelf = shelve.open(os.path.join(SHELF_DIR, "index_shelf")) + input_string = str(input_string) + + try: + int(input_string) # if not coercible into an integer, then its probably a repo name rather than ID + try: + name = index_shelf[input_string] + return Commands(name, name_shelf[name]) + except KeyError: + raise Exception("That index does not exist.") + return + except ValueError: + try: + return Commands(input_string, name_shelf[input_string]) + except KeyError: + raise Exception("That repository name does not exist") + return + index_shelf.close() + name_shelf.close() + +def load_multiple(*args, _all=False): + """Create `commands` object for a set of repositories + + Parameters + ------------ + args : int + comma-separated string values + + Yields + --------- + A list of commands objects. One for each of the entered string + """ + + if _all: + name_shelf = shelve.open(os.path.join(SHELF_DIR, "name_shelf")) + for key in name_shelf.keys(): + yield load(key) + else: + for each in args: + yield load(each) + +def pull(*args, _all=False): + """Pull all repositories""" + print("Pull repositories\n\n") + for each in load_multiple(_all=True): + print("***", each.name, "***") + print(each.pull()) + print() + +def push(*args, _all=False): + """Pull all repositories""" + print("Push directories\n\n") + for each in load_multiple(_all=True): + print("***", each.name, "***") + print(each.push(), "\n") + +def all_status(status_dir=STATUS_DIR): + """Write status of all repositories to text file""" + os.system("cls") + print("Getting repository status...Please be patient") + attention = [] + + try: + os.mkdir(DESKTOP) + except FileExistsError: + pass + try: + os.mkdir(status_dir) + except FileExistsError: + pass + os.chdir(status_dir) + + fname = "REPO_STATUS_@_{}.md".format(TIMING) + + with open(fname, 'w+') as fhand: + fhand.write("# Repository status as at {}".format(TIMING)) + fhand.write("\n\n") + for each in load_multiple(_all=True): + name = each.name + status = each.status() + + heading = "## {}".format(name) + fhand.write(heading) + fhand.write("\n\n") + fhand.write(status) + fhand.write("\n") + + if need_attention(status): + attention.append(name) + + fhand.write("-------") + fhand.write("\n## ATTENTION\n") + + attentions = ["{}. {}".format(index+1, name) for index, name in enumerate(attention)] + + fhand.write("\n".join(attentions)) + print("\n\nDone writing. Please check the status folder on your desktop") + os.chdir(BASE_DIR) + +if __name__ == "__main__": + initialize() diff --git a/build/lib/pygit/new.py b/build/lib/pygit/new.py new file mode 100644 index 0000000..bdec45f --- /dev/null +++ b/build/lib/pygit/new.py @@ -0,0 +1,475 @@ +"""pygit""" + +# https://stackoverflow.com/questions/19687394/python-script-to-determine-if-a-directory-is-a-git-repository +# http://gitpython.readthedocs.io/en/stable/ + +import os +import sys +import site +import shutil +import shelve +import argparse +from datetime import datetime +from subprocess import Popen, PIPE, STDOUT + +from send2trash import send2trash + +USERHOME = os.path.abspath(os.path.expanduser('~')) +DESKTOP = os.path.abspath(USERHOME + '/Desktop/') +TIMING = datetime.now().strftime("%a_%d_%b_%Y_%H_%M_%S_%p") + +STATUS_DIR = os.path.join(DESKTOP ,"status") +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +SHELF_DIR = os.path.join(BASE_DIR, "shelves") +TEST_DIR = os.path.join(BASE_DIR, "test_git/") + +def clear_screen(): + if sys.platform == 'win32': + os.system('cls') + if sys.platform == 'linux': + os.system('clear') + +def cleanup(): + """Cleanup files""" + send2trash(SHELF_DIR) + +# keep for later +def kill_process(process): + if process.poll() is None: # don't send the signal unless it seems it is necessary + try: + process.kill() + except PermissionError: # ignore + print("Os error. cannot kill kill_process") + pass + +def check_git_support(): + """Check if a git repo can be initialized in present shell""" + try: + os.mkdir(TEST_DIR) + except FileExistsError: + shutil.rmtree(TEST_DIR) + os.mkdir(TEST_DIR) + + os.chdir(TEST_DIR) + process = Popen("git init", shell=True, stdin=PIPE, stdout=PIPE, stderr=STDOUT,) + output, _ = process.communicate() + msg = str(output.decode("utf-8")) + # print(msg) + if "Initialized empty Git repository" in msg: + os.chdir(BASE_DIR) + shutil.rmtree(TEST_DIR) + return True + return False + +def is_git_repo(directory): + """Determine if a folder is a git repo + Checks for the presence of a .git folder inside a directory + """ + if ".git" in os.listdir(directory): + return True + return False + +def need_attention(status): + """Return True if a repo status is not same as that of remote""" + msg = ["staged", "behind", "ahead", "Untracked"] + if any([each in status for each in msg]): + return True + return False + +def parse_arguments(): + + """Initialize the data necessary for pygit to operate + + Parameters + ----------- + gitPath : str + Optional, Path to a git executable, if git is not in your system path. + masterDirectory : str + Optional, Full pathname to directory with multiple git repos + simpleDirectory : list + A list of full pathname to individual git repos. + + Notes + ------ + Accepts command line inputs only. + """ + parser = argparse.ArgumentParser(prog="Pygit. Initialize pygit's working directories.") + parser.add_argument("-v", "--verbosity", type=int, help="turn verbosity ON/OFF", choices=[0,1]) + parser.add_argument("-r", "--rules", help="Set a list of string patterns for folders you don't want pygit to touch", nargs='+') + parser.add_argument('-g', '--gitPath', help="Full pathname to git executable. cmd or bash.") + parser.add_argument('-m', '--masterDirectory', help="Full pathname to directory with multiple git repos.") + parser.add_argument('-s', '--simpleDirectory', help="A list of full pathname to individual git repos.", nargs='+') + parser.add_argument('-t', '--statusDirectory', help="Full pathname to directory for writing status.") # make mandatory + + args = parser.parse_args() + return args.verbosity, args.rules, args.gitPath, args.masterDirectory, args.simpleDirectory, args.statusDirectory + + +def initialize(verbosity): + clear_screen() + try: + os.mkdir(SHELF_DIR) + except FileExistsError: + shutil.rmtree(SHELF_DIR) + os.mkdir(SHELF_DIR) + + name_shelf = shelve.open(os.path.join(SHELF_DIR, "name_shelf")) + index_shelf = shelve.open(os.path.join(SHELF_DIR, "index_shelf")) + + if args.gitPath: + for _, __, files in os.walk(args.gitPath): + if "git-cmd.exe" in files: + name_shelf['GIT_WINDOWS'] = args.gitPath + elif "git-bash.exe" in files: + name_shelf['GIT_BASH'] = args.gitPath + else: + print("A valid git executable was not found in the directory.\n") + pass + + else: + if check_git_support(): + if args.verbosity: + print("Your system supports git out of the box.\n") + elif "git" in os.environ['PATH']: + user_paths = os.environ['PATH'].split(os.pathsep) + for path in user_paths: + if "git-cmd.exe" in path: + name_shelf['GIT_WINDOWS'] = path + break + if "git-bash.exe" in path: + name_shelf['GIT_BASH'] = path + break + else: + print("Git was not found in your system path.\nYou may need to set the location manually using the -g flag.\n") + + if args.masterDirectory: + + if args.verbosity: + print("Master directory set to ", args.masterDirectory, "\n") + print("Now working on folders ... Please wait a few minutes.\n") + + i = len(list(index_shelf.keys())) + 1 + for path, _, __ in os.walk(args.masterDirectory): + if path.startswith("."): + continue + if args.rules: + # if any of the string patterns is found in path name, that folder will be skipped. + if any([rule in path for rule in args.rules]): + if args.verbosity: + print(path, " matches a rule. Skipping") + continue + + directory_absolute_path = os.path.abspath(path) + + if is_git_repo(directory_absolute_path): + if sys.platform == 'win32': + name = directory_absolute_path.split("\\")[-1] + if sys.platform == 'linux': + name = directory_absolute_path.split("/")[-1] + + name_shelf[name] = directory_absolute_path + index_shelf[str(i)] = name + i += 1 + name_shelf.close() + index_shelf.close() + + if args.simpleDirectory: + if args.verbosity: + print("Now shelving the following directories\n") + print(args.simpleDirectory) + + i = len(list(index_shelf.keys())) + 1 + for directory in args.simpleDirectory: + + if is_git_repo(directory): + if sys.platform == 'win32': + name = directory.split("\\")[-1] + if sys.platform == 'linux': + name = directory.split("/")[-1] + + name_shelf[name] = directory + index_shelf[str(i)] = name + else: + print(directory, " is not a valid git repo.\nContinuing...") + continue + i += 1 + + name_shelf.close() + index_shelf.close() + + global STATUS_DIR + if args.statusDirectory: + STATUS_DIR = args.statusDirectory + if args.verbosity: + print("\nStatus files to be saved in {}\n".format(STATUS_DIR)) + else: + if args.verbosity: + print("\nStatus files to be saved in {}\n".format(STATUS_DIR)) + + if args.verbosity: + print("\nDone.\nThe following directories were set.\n") + name_shelf = shelve.open(os.path.join(SHELF_DIR, "name_shelf")) + index_shelf = shelve.open(os.path.join(SHELF_DIR, "index_shelf")) + + print("{:<4} {:<20} {:<}".format("Key", "| Name", "| Path")) + print("****************************") + for key in index_shelf.keys(): + name = index_shelf[key] + print("{:<4} {:<20} {:<}".format(key, name, name_shelf[name])) + index_shelf.close() + name_shelf.close() + return + +class Commands: + """Commands class + + Parameters + ----------- + repo_name : str + The repository name. See list of repositories by running + master_directory : str + The absolute path to the directory + git_exec : str + The path to the git executable on the system + message : str + Commit message + + Returns + -------- + : Commands object + """ + + def __str__(self): + return "Commands: {}: {}".format(self.name, self.dir) + + def __eq__(self, other): + if isinstance(other, self.__class__): + return self.__dict__ == other.__dict__ + else: + return False + + def __init__(self, repo_name, master_directory, git_exec=None, message="minor changes"): + self.name = repo_name + self.dir = master_directory + self.git_exec = git_exec + self.message = message + + try: + os.chdir(self.dir) + except (FileNotFoundError, TypeError): + print("{} may have been moved.\n Run initialize() to update paths".format(self.name)) + self.dir = os.getcwd() + + def fetch(self): + """git fetch""" + if self.git_exec: + process = Popen([self.git_exec, "git fetch"], stdin=PIPE, stdout=PIPE, stderr=STDOUT) + else: + process = Popen("git fetch", shell=True, stdin=PIPE, stdout=PIPE, stderr=STDOUT) + # output, error = process.communicate() + process.communicate() + + def status(self): + """git status""" + self.fetch() # always do a fetch before reporting status + if self.git_exec: + process = Popen([self.git_exec, "git status"], stdin=PIPE, stdout=PIPE, stderr=STDOUT) + else: + process = Popen("git status", shell=True, stdin=PIPE, stdout=PIPE, stderr=STDOUT) + output, _ = process.communicate() + return str(output.decode("utf-8")) + + def stage_file(self, file_name): + """git add file""" + stage_file = 'git add {}'.format(file_name) + if self.git_exec: + process = Popen([self.git_exec, stage_file], stdin=PIPE, stdout=PIPE, stderr=STDOUT,) + else: + process = Popen(stage_file, shell=True, stdin=PIPE, stdout=PIPE, stderr=STDOUT,) + + output, _ = process.communicate() + return str(output.decode("utf-8")) + + def stage_all(self, files="."): + """git add all""" + files = "` ".join(files.split()) + stage_file = 'git add {}'.format(files) + if self.git_exec: + process = Popen([self.git_exec, stage_file], stdin=PIPE, stdout=PIPE, stderr=STDOUT,) + else: + process = Popen(stage_file, shell=True, stdin=PIPE, stdout=PIPE, stderr=STDOUT,) + + process = Popen([self.git_exec, stage_file], stdin=PIPE, stdout=PIPE, stderr=STDOUT,) + output, _ = process.communicate() + return str(output.decode("utf-8")) + + def commit(self): + """git commit""" + enter = input("Commit message.\nPress enter to use 'minor changes'") + if enter == "": + message = self.message + else: + message = enter + # message = "` ".join(message.split()) + if self.git_exec: + process = Popen([self.git_exec, 'git', 'commit', '-m', message], stdin=PIPE, stdout=PIPE, stderr=PIPE,) + else: + process = Popen(['git', 'commit', '-m', message], shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE,) + output, _ = process.communicate() + return str(output.decode("utf-8")) + + def stage_and_commit(self): + """git add followed by commit""" + self.stage_all() + self.commit() + + def push(self): + """git push""" + process = Popen([self.git_exec, 'git push'], stdin=PIPE, stdout=PIPE, stderr=STDOUT,) + output, _ = process.communicate() + return str("Push completed.{}".format(str(output.decode("utf-8")))) + + def pull(self): + """git pull""" + process = Popen([self.git_exec, 'git pull'], stdin=PIPE, stdout=PIPE, stderr=STDOUT,) + output, _ = process.communicate() + return str("Pull completed.\n{}".format(str(output.decode("utf-8")))) + + def reset(self, number='1'): + """git reset""" + process = Popen([self.git_exec, 'git reset HEAD~', number], stdin=PIPE, stdout=PIPE, stderr=STDOUT,) + output, _ = process.communicate() + return str(output.decode("utf-8")) + + # def branch(self): + # """Return the branch being tracked by local""" + # process = Popen([self.git_exec, 'git branch -vv'], shell=True, + # stdin=PIPE, stdout=PIPE, stderr=STDOUT,) + # output, _ = process.communicate() + # out_text = str(output.decode("utf-8")) + # try: + # line = [each for each in out_text.split("\n") if each.startswith("*")][0] + # except IndexError: # no lines start with * + # return + # branch_name = re.search(r"\[origin\/(.*)\]", line) + # return branch_name.group(1) + +def show_repos(): + """Show all available repositories, path, and unique ID""" + clear_screen() + print("\nThe following repos are available.\n") + name_shelf = shelve.open(os.path.join(SHELF_DIR, "name_shelf")) + index_shelf = shelve.open(os.path.join(SHELF_DIR, "index_shelf")) + + print("{:<4} {:<20} {:<}".format("Key", "| Name", "| Path")) + print("******************************************") + for key in index_shelf.keys(): + name = index_shelf[key] + print("{:<4} {:<20} {:<}".format(key, name, name_shelf[name])) + index_shelf.close() + name_shelf.close() + +def load(input_string): # id is string + """Load a repository with specified id""" + name_shelf = shelve.open(os.path.join(SHELF_DIR, "name_shelf")) + index_shelf = shelve.open(os.path.join(SHELF_DIR, "index_shelf")) + input_string = str(input_string) + + try: + int(input_string) # if not coercible into an integer, then its probably a repo name rather than ID + try: + name = index_shelf[input_string] + return Commands(name, name_shelf[name]) + except KeyError: + raise Exception("That index does not exist.") + return + except ValueError: + try: + return Commands(input_string, name_shelf[input_string]) + except KeyError: + raise Exception("That repository name does not exist") + return + index_shelf.close() + name_shelf.close() + +def load_multiple(*args, _all=False): + """Create `commands` object for a set of repositories + + Parameters + ------------ + args : int + comma-separated string values + + Yields + --------- + A list of commands objects. One for each of the entered string + """ + + if _all: + name_shelf = shelve.open(os.path.join(SHELF_DIR, "name_shelf")) + for key in name_shelf.keys(): + yield load(key) + else: + for each in args: + yield load(each) + +def pull(*args, _all=False): + """Pull all repositories""" + print("Pull repositories\n\n") + for each in load_multiple(_all=True): + print("***", each.name, "***") + print(each.pull()) + print() + +def push(*args, _all=False): + """Pull all repositories""" + print("Push directories\n\n") + for each in load_multiple(_all=True): + print("***", each.name, "***") + print(each.push(), "\n") + +def all_status(status_dir=STATUS_DIR): + """Write status of all repositories to text file""" + os.system("cls") + print("Getting repository status...Please be patient") + attention = [] + + try: + os.mkdir(DESKTOP) + except FileExistsError: + pass + try: + os.mkdir(status_dir) + except FileExistsError: + pass + os.chdir(status_dir) + + fname = "REPO_STATUS_@_{}.md".format(TIMING) + + with open(fname, 'w+') as fhand: + fhand.write("# Repository status as at {}".format(TIMING)) + fhand.write("\n\n") + for each in load_multiple(_all=True): + name = each.name + status = each.status() + + heading = "## {}".format(name) + fhand.write(heading) + fhand.write("\n\n") + fhand.write(status) + fhand.write("\n") + + if need_attention(status): + attention.append(name) + + fhand.write("-------") + fhand.write("\n## ATTENTION\n") + + attentions = ["{}. {}".format(index+1, name) for index, name in enumerate(attention)] + + fhand.write("\n".join(attentions)) + print("\n\nDone writing. Please check the status folder on your desktop") + os.chdir(BASE_DIR) + +if __name__ == "__main__": + initialize() diff --git a/build/lib/pygit/select_directory.py b/build/lib/pygit/select_directory.py new file mode 100644 index 0000000..d491078 --- /dev/null +++ b/build/lib/pygit/select_directory.py @@ -0,0 +1,22 @@ +"""Graphical directory selection""" + +try: + import tkinter as tk + from tkinter import filedialog +except ImportError: + print("tkinter was not found.") + pass + + +def select_directory(title="Select directory"): + """Select and return a directory path""" + try: + root = tk.Tk() + root.withdraw() + initialdir = "/" + root_dir = filedialog.askdirectory(parent=root, + initialdir=initialdir, + title=title) + except: + print("Tk inter was not found.") + return root_dir diff --git a/build/lib/pygit/test.py b/build/lib/pygit/test.py new file mode 100644 index 0000000..318be9d --- /dev/null +++ b/build/lib/pygit/test.py @@ -0,0 +1,149 @@ +"""Test API""" + +import json +import os +import random +import unittest +from glob import glob +from random import choice +from send2trash import send2trash +# import inspect + +from .pygit import ( + USERHOME, DESKTOP, STATUS_DIR, BASE_DIR, SHELF_DIR, TEST_DIR, TIMING, + cleanup, check_git_support, is_git_repo, need_attention, initialize, + Commands, show_repos, load, load_multiple, pull, push, all_status +) + +class TestAppSetupA(unittest.TestCase): + """Basic setup to test API""" + def setUp(self): + """Setup""" + print("Start ", os.path.basename(__file__), " tests") + initialize() + + def test_ids_exist(self): # always regen for this test to work + """Test id file created and that its content is a dictionary""" + assert os.path.exists(IDS) + with open(IDS) as rhand: + self.assertIsInstance(json.load(rhand), dict) + +class TestAppSetupB(unittest.TestCase): + """main body of tests""" + def test_loader(self): + """Test Loader returns an instance of Commands and it has all attributes""" + with open(IDS, "r") as rhand: + ids = json.load(rhand) + random_id = random.choice(list(ids.keys())) + opobj = load(repo_id=random_id) + self.assertIsInstance(opobj, Commands) + # methods = [each[0] for each in inspect.getmembers(opobj, predicate=inspect.ismethod)] + +class TestCommandsA(unittest.TestCase): # set up + """Test git commands""" + def setUp(self): + """Pick a repo""" + print("Start ", os.path.basename(__file__), " tests") + set_all(git_type="win") + + def test_dummy_setup(self): + """Dummy to setup our files""" + assert os.path.exists(REPO_PATH) + +class TestCommandsB(unittest.TestCase): + def setUp(self): + """Set up path""" + with open(EXEC_PATH, "r") as rhand: + self.exec_ = json.load(rhand) + self.git = self.exec_["git"] + + with open(IDS, "r") as rhand: + self.reps = json.load(rhand) + + self.ids = list(self.reps.keys()) + self.pick = choice(self.ids) + + def test_git_status(self): + """Check for the text 'On branch master'""" + name_obj = load(repo_id=self.pick) + name_obj.fetch() + self.assertIn("On branch master", name_obj.status()) + + def test_git_add(self): + """Check for 'Changes not staged for commit:' + """ + for each in self.ids: + git_obj = load(repo_id=each) + status = git_obj.status() + + if "nothing to commit" in status: + continue + elif "Changes not staged for commit:" in status: + git_obj.add_all() + status = git_obj.status() + checks = [each in status for each in ["modified", "new file", "deleted"]] + self.assertTrue(any(checks)) + else: + return "No changes to commit" + + def test_git_commit(self): + """Check branch is ahead or behind + """ + for each in self.ids: + git_obj = load(repo_id=each) + status = git_obj.status() + + if "nothing to commit" in status: + continue + else: + git_obj.add_all() + git_obj.commit() + status = git_obj.status() + self.assertTrue(any( + ['is ahead' in status], ['is behind' in status], + ['not staged' in status], 'changes' in status)) + +class TestUtilsA(unittest.TestCase): + """Test that search paths exist""" + def setUp(self): + print("Start ", os.path.basename(__file__), " tests") + set_search_paths() + set_input_data() + + def test_set_search_paths(self): + """Test search directory created""" + assert os.path.exists(SEARCH_PATHS) + +class TestUtilsB(unittest.TestCase): + """Test Utils""" + def setUp(self): + self.repos, self.names, self.exec_ = get_repos_and_git() + + def test_repo_path_exists(self): + """Test repo directory created""" + assert os.path.exists(REPO_PATH) + + def test_git_present(self): + """Docstring""" + self.assertTrue(self.exec_["win"]) + + def test_repos_are_directories(self): + """Test all git repos are directories""" + for repo, name in zip(self.repos, self.names): + self.assertTrue(os.path.isdir(repo)) + self.assertIsInstance(name, str) + +class TestZ(unittest.TestCase): # remove after adding test data + """Clean up files""" + def setUp(self): + pass + + def tearDown(self): + self._ = send2trash(each) + + def test_dummy(self): + """Trigger tearDown""" + self.assertFalse(os.path.exists(BASE_PATH)) + +if __name__ == "__main__": + unittest.main() diff --git a/dist/PyGit-3.0-py3.6.egg b/dist/PyGit-3.0-py3.6.egg new file mode 100644 index 0000000..1f99cbc Binary files /dev/null and b/dist/PyGit-3.0-py3.6.egg differ diff --git a/pygit/new.py b/pygit/new.py new file mode 100644 index 0000000..bdec45f --- /dev/null +++ b/pygit/new.py @@ -0,0 +1,475 @@ +"""pygit""" + +# https://stackoverflow.com/questions/19687394/python-script-to-determine-if-a-directory-is-a-git-repository +# http://gitpython.readthedocs.io/en/stable/ + +import os +import sys +import site +import shutil +import shelve +import argparse +from datetime import datetime +from subprocess import Popen, PIPE, STDOUT + +from send2trash import send2trash + +USERHOME = os.path.abspath(os.path.expanduser('~')) +DESKTOP = os.path.abspath(USERHOME + '/Desktop/') +TIMING = datetime.now().strftime("%a_%d_%b_%Y_%H_%M_%S_%p") + +STATUS_DIR = os.path.join(DESKTOP ,"status") +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +SHELF_DIR = os.path.join(BASE_DIR, "shelves") +TEST_DIR = os.path.join(BASE_DIR, "test_git/") + +def clear_screen(): + if sys.platform == 'win32': + os.system('cls') + if sys.platform == 'linux': + os.system('clear') + +def cleanup(): + """Cleanup files""" + send2trash(SHELF_DIR) + +# keep for later +def kill_process(process): + if process.poll() is None: # don't send the signal unless it seems it is necessary + try: + process.kill() + except PermissionError: # ignore + print("Os error. cannot kill kill_process") + pass + +def check_git_support(): + """Check if a git repo can be initialized in present shell""" + try: + os.mkdir(TEST_DIR) + except FileExistsError: + shutil.rmtree(TEST_DIR) + os.mkdir(TEST_DIR) + + os.chdir(TEST_DIR) + process = Popen("git init", shell=True, stdin=PIPE, stdout=PIPE, stderr=STDOUT,) + output, _ = process.communicate() + msg = str(output.decode("utf-8")) + # print(msg) + if "Initialized empty Git repository" in msg: + os.chdir(BASE_DIR) + shutil.rmtree(TEST_DIR) + return True + return False + +def is_git_repo(directory): + """Determine if a folder is a git repo + Checks for the presence of a .git folder inside a directory + """ + if ".git" in os.listdir(directory): + return True + return False + +def need_attention(status): + """Return True if a repo status is not same as that of remote""" + msg = ["staged", "behind", "ahead", "Untracked"] + if any([each in status for each in msg]): + return True + return False + +def parse_arguments(): + + """Initialize the data necessary for pygit to operate + + Parameters + ----------- + gitPath : str + Optional, Path to a git executable, if git is not in your system path. + masterDirectory : str + Optional, Full pathname to directory with multiple git repos + simpleDirectory : list + A list of full pathname to individual git repos. + + Notes + ------ + Accepts command line inputs only. + """ + parser = argparse.ArgumentParser(prog="Pygit. Initialize pygit's working directories.") + parser.add_argument("-v", "--verbosity", type=int, help="turn verbosity ON/OFF", choices=[0,1]) + parser.add_argument("-r", "--rules", help="Set a list of string patterns for folders you don't want pygit to touch", nargs='+') + parser.add_argument('-g', '--gitPath', help="Full pathname to git executable. cmd or bash.") + parser.add_argument('-m', '--masterDirectory', help="Full pathname to directory with multiple git repos.") + parser.add_argument('-s', '--simpleDirectory', help="A list of full pathname to individual git repos.", nargs='+') + parser.add_argument('-t', '--statusDirectory', help="Full pathname to directory for writing status.") # make mandatory + + args = parser.parse_args() + return args.verbosity, args.rules, args.gitPath, args.masterDirectory, args.simpleDirectory, args.statusDirectory + + +def initialize(verbosity): + clear_screen() + try: + os.mkdir(SHELF_DIR) + except FileExistsError: + shutil.rmtree(SHELF_DIR) + os.mkdir(SHELF_DIR) + + name_shelf = shelve.open(os.path.join(SHELF_DIR, "name_shelf")) + index_shelf = shelve.open(os.path.join(SHELF_DIR, "index_shelf")) + + if args.gitPath: + for _, __, files in os.walk(args.gitPath): + if "git-cmd.exe" in files: + name_shelf['GIT_WINDOWS'] = args.gitPath + elif "git-bash.exe" in files: + name_shelf['GIT_BASH'] = args.gitPath + else: + print("A valid git executable was not found in the directory.\n") + pass + + else: + if check_git_support(): + if args.verbosity: + print("Your system supports git out of the box.\n") + elif "git" in os.environ['PATH']: + user_paths = os.environ['PATH'].split(os.pathsep) + for path in user_paths: + if "git-cmd.exe" in path: + name_shelf['GIT_WINDOWS'] = path + break + if "git-bash.exe" in path: + name_shelf['GIT_BASH'] = path + break + else: + print("Git was not found in your system path.\nYou may need to set the location manually using the -g flag.\n") + + if args.masterDirectory: + + if args.verbosity: + print("Master directory set to ", args.masterDirectory, "\n") + print("Now working on folders ... Please wait a few minutes.\n") + + i = len(list(index_shelf.keys())) + 1 + for path, _, __ in os.walk(args.masterDirectory): + if path.startswith("."): + continue + if args.rules: + # if any of the string patterns is found in path name, that folder will be skipped. + if any([rule in path for rule in args.rules]): + if args.verbosity: + print(path, " matches a rule. Skipping") + continue + + directory_absolute_path = os.path.abspath(path) + + if is_git_repo(directory_absolute_path): + if sys.platform == 'win32': + name = directory_absolute_path.split("\\")[-1] + if sys.platform == 'linux': + name = directory_absolute_path.split("/")[-1] + + name_shelf[name] = directory_absolute_path + index_shelf[str(i)] = name + i += 1 + name_shelf.close() + index_shelf.close() + + if args.simpleDirectory: + if args.verbosity: + print("Now shelving the following directories\n") + print(args.simpleDirectory) + + i = len(list(index_shelf.keys())) + 1 + for directory in args.simpleDirectory: + + if is_git_repo(directory): + if sys.platform == 'win32': + name = directory.split("\\")[-1] + if sys.platform == 'linux': + name = directory.split("/")[-1] + + name_shelf[name] = directory + index_shelf[str(i)] = name + else: + print(directory, " is not a valid git repo.\nContinuing...") + continue + i += 1 + + name_shelf.close() + index_shelf.close() + + global STATUS_DIR + if args.statusDirectory: + STATUS_DIR = args.statusDirectory + if args.verbosity: + print("\nStatus files to be saved in {}\n".format(STATUS_DIR)) + else: + if args.verbosity: + print("\nStatus files to be saved in {}\n".format(STATUS_DIR)) + + if args.verbosity: + print("\nDone.\nThe following directories were set.\n") + name_shelf = shelve.open(os.path.join(SHELF_DIR, "name_shelf")) + index_shelf = shelve.open(os.path.join(SHELF_DIR, "index_shelf")) + + print("{:<4} {:<20} {:<}".format("Key", "| Name", "| Path")) + print("****************************") + for key in index_shelf.keys(): + name = index_shelf[key] + print("{:<4} {:<20} {:<}".format(key, name, name_shelf[name])) + index_shelf.close() + name_shelf.close() + return + +class Commands: + """Commands class + + Parameters + ----------- + repo_name : str + The repository name. See list of repositories by running + master_directory : str + The absolute path to the directory + git_exec : str + The path to the git executable on the system + message : str + Commit message + + Returns + -------- + : Commands object + """ + + def __str__(self): + return "Commands: {}: {}".format(self.name, self.dir) + + def __eq__(self, other): + if isinstance(other, self.__class__): + return self.__dict__ == other.__dict__ + else: + return False + + def __init__(self, repo_name, master_directory, git_exec=None, message="minor changes"): + self.name = repo_name + self.dir = master_directory + self.git_exec = git_exec + self.message = message + + try: + os.chdir(self.dir) + except (FileNotFoundError, TypeError): + print("{} may have been moved.\n Run initialize() to update paths".format(self.name)) + self.dir = os.getcwd() + + def fetch(self): + """git fetch""" + if self.git_exec: + process = Popen([self.git_exec, "git fetch"], stdin=PIPE, stdout=PIPE, stderr=STDOUT) + else: + process = Popen("git fetch", shell=True, stdin=PIPE, stdout=PIPE, stderr=STDOUT) + # output, error = process.communicate() + process.communicate() + + def status(self): + """git status""" + self.fetch() # always do a fetch before reporting status + if self.git_exec: + process = Popen([self.git_exec, "git status"], stdin=PIPE, stdout=PIPE, stderr=STDOUT) + else: + process = Popen("git status", shell=True, stdin=PIPE, stdout=PIPE, stderr=STDOUT) + output, _ = process.communicate() + return str(output.decode("utf-8")) + + def stage_file(self, file_name): + """git add file""" + stage_file = 'git add {}'.format(file_name) + if self.git_exec: + process = Popen([self.git_exec, stage_file], stdin=PIPE, stdout=PIPE, stderr=STDOUT,) + else: + process = Popen(stage_file, shell=True, stdin=PIPE, stdout=PIPE, stderr=STDOUT,) + + output, _ = process.communicate() + return str(output.decode("utf-8")) + + def stage_all(self, files="."): + """git add all""" + files = "` ".join(files.split()) + stage_file = 'git add {}'.format(files) + if self.git_exec: + process = Popen([self.git_exec, stage_file], stdin=PIPE, stdout=PIPE, stderr=STDOUT,) + else: + process = Popen(stage_file, shell=True, stdin=PIPE, stdout=PIPE, stderr=STDOUT,) + + process = Popen([self.git_exec, stage_file], stdin=PIPE, stdout=PIPE, stderr=STDOUT,) + output, _ = process.communicate() + return str(output.decode("utf-8")) + + def commit(self): + """git commit""" + enter = input("Commit message.\nPress enter to use 'minor changes'") + if enter == "": + message = self.message + else: + message = enter + # message = "` ".join(message.split()) + if self.git_exec: + process = Popen([self.git_exec, 'git', 'commit', '-m', message], stdin=PIPE, stdout=PIPE, stderr=PIPE,) + else: + process = Popen(['git', 'commit', '-m', message], shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE,) + output, _ = process.communicate() + return str(output.decode("utf-8")) + + def stage_and_commit(self): + """git add followed by commit""" + self.stage_all() + self.commit() + + def push(self): + """git push""" + process = Popen([self.git_exec, 'git push'], stdin=PIPE, stdout=PIPE, stderr=STDOUT,) + output, _ = process.communicate() + return str("Push completed.{}".format(str(output.decode("utf-8")))) + + def pull(self): + """git pull""" + process = Popen([self.git_exec, 'git pull'], stdin=PIPE, stdout=PIPE, stderr=STDOUT,) + output, _ = process.communicate() + return str("Pull completed.\n{}".format(str(output.decode("utf-8")))) + + def reset(self, number='1'): + """git reset""" + process = Popen([self.git_exec, 'git reset HEAD~', number], stdin=PIPE, stdout=PIPE, stderr=STDOUT,) + output, _ = process.communicate() + return str(output.decode("utf-8")) + + # def branch(self): + # """Return the branch being tracked by local""" + # process = Popen([self.git_exec, 'git branch -vv'], shell=True, + # stdin=PIPE, stdout=PIPE, stderr=STDOUT,) + # output, _ = process.communicate() + # out_text = str(output.decode("utf-8")) + # try: + # line = [each for each in out_text.split("\n") if each.startswith("*")][0] + # except IndexError: # no lines start with * + # return + # branch_name = re.search(r"\[origin\/(.*)\]", line) + # return branch_name.group(1) + +def show_repos(): + """Show all available repositories, path, and unique ID""" + clear_screen() + print("\nThe following repos are available.\n") + name_shelf = shelve.open(os.path.join(SHELF_DIR, "name_shelf")) + index_shelf = shelve.open(os.path.join(SHELF_DIR, "index_shelf")) + + print("{:<4} {:<20} {:<}".format("Key", "| Name", "| Path")) + print("******************************************") + for key in index_shelf.keys(): + name = index_shelf[key] + print("{:<4} {:<20} {:<}".format(key, name, name_shelf[name])) + index_shelf.close() + name_shelf.close() + +def load(input_string): # id is string + """Load a repository with specified id""" + name_shelf = shelve.open(os.path.join(SHELF_DIR, "name_shelf")) + index_shelf = shelve.open(os.path.join(SHELF_DIR, "index_shelf")) + input_string = str(input_string) + + try: + int(input_string) # if not coercible into an integer, then its probably a repo name rather than ID + try: + name = index_shelf[input_string] + return Commands(name, name_shelf[name]) + except KeyError: + raise Exception("That index does not exist.") + return + except ValueError: + try: + return Commands(input_string, name_shelf[input_string]) + except KeyError: + raise Exception("That repository name does not exist") + return + index_shelf.close() + name_shelf.close() + +def load_multiple(*args, _all=False): + """Create `commands` object for a set of repositories + + Parameters + ------------ + args : int + comma-separated string values + + Yields + --------- + A list of commands objects. One for each of the entered string + """ + + if _all: + name_shelf = shelve.open(os.path.join(SHELF_DIR, "name_shelf")) + for key in name_shelf.keys(): + yield load(key) + else: + for each in args: + yield load(each) + +def pull(*args, _all=False): + """Pull all repositories""" + print("Pull repositories\n\n") + for each in load_multiple(_all=True): + print("***", each.name, "***") + print(each.pull()) + print() + +def push(*args, _all=False): + """Pull all repositories""" + print("Push directories\n\n") + for each in load_multiple(_all=True): + print("***", each.name, "***") + print(each.push(), "\n") + +def all_status(status_dir=STATUS_DIR): + """Write status of all repositories to text file""" + os.system("cls") + print("Getting repository status...Please be patient") + attention = [] + + try: + os.mkdir(DESKTOP) + except FileExistsError: + pass + try: + os.mkdir(status_dir) + except FileExistsError: + pass + os.chdir(status_dir) + + fname = "REPO_STATUS_@_{}.md".format(TIMING) + + with open(fname, 'w+') as fhand: + fhand.write("# Repository status as at {}".format(TIMING)) + fhand.write("\n\n") + for each in load_multiple(_all=True): + name = each.name + status = each.status() + + heading = "## {}".format(name) + fhand.write(heading) + fhand.write("\n\n") + fhand.write(status) + fhand.write("\n") + + if need_attention(status): + attention.append(name) + + fhand.write("-------") + fhand.write("\n## ATTENTION\n") + + attentions = ["{}. {}".format(index+1, name) for index, name in enumerate(attention)] + + fhand.write("\n".join(attentions)) + print("\n\nDone writing. Please check the status folder on your desktop") + os.chdir(BASE_DIR) + +if __name__ == "__main__": + initialize() diff --git a/setup.py b/setup.py index 46f7b39..a1f76fd 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,53 @@ """Setup""" +# https://stackoverflow.com/questions/20288711/post-install-script-with-python-setuptools?rq=1 +# https://stackoverflow.com/questions/17806485/execute-a-python-script-post-install-using-distutils-setuptools +# https://pymotw.com/2/site/#module-sitecustomize +import os +import sys +import site +from subprocess import call from setuptools import setup +from setuptools.command.install import install as _install + +def get_package_install_directory(): + """Return the installation directory, or None""" + if '--user' in sys.argv: + paths = (site.getusersitepackages(),) + else: + py_version = '%s.%s' % (sys.version_info[0], sys.version_info[1]) + paths = (s % (py_version) for s in ( + sys.prefix + '/lib/python%s/dist-packages/', + sys.prefix + '/lib/python%s/site-packages/', + sys.prefix + '/local/lib/python%s/dist-packages/', + sys.prefix + '/local/lib/python%s/site-packages/', + '/Library/Python/%s/site-packages/', + )) + paths = tuple(paths) + ( + # these paths likely work for anaconda install + sys.prefix + '/lib/dist-packages/', + sys.prefix + '/lib/site-packages/', + sys.prefix + '/local/lib/dist-packages/', + sys.prefix + '/local/lib/site-packages/', + ) + + for path in paths: + if os.path.exists(path): + print("++++++++path exists", path) + install_path = path + 'chi'# build path to file + site.addsitedir(install_path) # add the installation dir to path. my main aim + return path + print('no installation path found', file=sys.stderr) + return None + +def _post_install(dir): + get_package_install_directory() + +class install(_install): + def run(self): + _install.run(self) + self.execute(_post_install, (self.install_lib,), + msg="Running post install task") def readme(): """Readme""" @@ -26,5 +73,8 @@ def readme(): 'send2trash' ], zip_safe=False, - test_suite='nose2.collector.collector', - test_requires=["nose2"]) + test_suite='nose2.collector.collector',) + +setup( + cmdclass={'install': install}, +)