From f40e658ef9cfc728645d0d471a8e1d7f31a25167 Mon Sep 17 00:00:00 2001 From: immensity Date: Tue, 20 Mar 2018 00:25:35 +0400 Subject: [PATCH] working on setup.py --- PyGit.egg-info/PKG-INFO | 178 ++++++++++ PyGit.egg-info/SOURCES.txt | 16 + PyGit.egg-info/dependency_links.txt | 1 + PyGit.egg-info/not-zip-safe | 1 + PyGit.egg-info/requires.txt | 1 + PyGit.egg-info/top_level.txt | 1 + UNKNOWN.egg-info/PKG-INFO | 8 + UNKNOWN.egg-info/SOURCES.txt | 8 + UNKNOWN.egg-info/dependency_links.txt | 1 + UNKNOWN.egg-info/top_level.txt | 1 + build/lib/pygit/__init__.py | 9 + build/lib/pygit/binn.py | 45 +++ build/lib/pygit/main.py | 471 +++++++++++++++++++++++++ build/lib/pygit/new.py | 475 ++++++++++++++++++++++++++ build/lib/pygit/select_directory.py | 22 ++ build/lib/pygit/test.py | 149 ++++++++ dist/PyGit-3.0-py3.6.egg | Bin 0 -> 33115 bytes pygit/new.py | 475 ++++++++++++++++++++++++++ setup.py | 54 ++- 19 files changed, 1914 insertions(+), 2 deletions(-) create mode 100644 PyGit.egg-info/PKG-INFO create mode 100644 PyGit.egg-info/SOURCES.txt create mode 100644 PyGit.egg-info/dependency_links.txt create mode 100644 PyGit.egg-info/not-zip-safe create mode 100644 PyGit.egg-info/requires.txt create mode 100644 PyGit.egg-info/top_level.txt create mode 100644 UNKNOWN.egg-info/PKG-INFO create mode 100644 UNKNOWN.egg-info/SOURCES.txt create mode 100644 UNKNOWN.egg-info/dependency_links.txt create mode 100644 UNKNOWN.egg-info/top_level.txt create mode 100644 build/lib/pygit/__init__.py create mode 100644 build/lib/pygit/binn.py create mode 100644 build/lib/pygit/main.py create mode 100644 build/lib/pygit/new.py create mode 100644 build/lib/pygit/select_directory.py create mode 100644 build/lib/pygit/test.py create mode 100644 dist/PyGit-3.0-py3.6.egg create mode 100644 pygit/new.py 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 0000000000000000000000000000000000000000..1f99cbcf1272595bb35ecd68767c0aeac6fa5e23 GIT binary patch literal 33115 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 qGQdCa|Ewkag|Be