diff --git a/docs/docs.zip b/docs/docs.zip new file mode 100644 index 0000000..2e87466 Binary files /dev/null and b/docs/docs.zip differ diff --git a/git-hub b/git-hub index eb00219..1991ba0 100755 --- a/git-hub +++ b/git-hub @@ -19,7 +19,6 @@ def login(): For example consider running 'git remote -v' and getting back: origin git@github.com:test_user/test_repo.git (fetch) origin git@github.com:test_user/test_repo.git (push) - Returns ------- username : string @@ -28,7 +27,6 @@ def login(): origin repository of the github user, i.e. test_repo remotes : string Git remotes: the output of 'git remote -v' - """ process = subprocess.Popen(["git", "remote", "-v"], stdout=subprocess.PIPE) remotes = str(process.stdout.read()) @@ -42,7 +40,6 @@ def login(): def get_token(): """Retrieve token from configuration file. - Returns ------- token : string @@ -61,26 +58,20 @@ def get_token(): else: print(textwrap.dedent("""\ No authentication token specified in: ~/.config/git-hub.yaml - Please see - https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/ - for instruction on obtaining a token. Then update the configuration file as follows: - token = abc123 """)) sys.exit(1) def pr(num): """Pulls down and checkout the branch of the pr. - This will run the following commands in the terminal: "git remote add user git@github.com:user/repo", "git fetch user", "git checkout -b pr/num user/branch" - Parameters ---------- num : integer @@ -121,10 +112,8 @@ def pr(num): def push(): """Pushes changes back to a branch. - This will run the following command: "git push user pr/num:branch" - """ p = subprocess.Popen(["git", "branch"], stdout=subprocess.PIPE) pr = str(p.stdout.read()) @@ -169,7 +158,6 @@ def path_to_toml(): def find_in_dictionary(pr_id, pr_data): """Fetches all the information on a certain PR - Parameters ---------- pr_id : integer @@ -186,7 +174,6 @@ def find_in_dictionary(pr_id, pr_data): def get_info(pr_data): """Fetches all the information on a certain PR - Parameters ---------- pr_data : integer @@ -236,12 +223,13 @@ def get_info(pr_data): def parse_time(time): """ Converts time into python datetime object - Parameters ---------- time : String Time that git-hub gives """ + print(time) + print(datetime.strptime(time)) date = time.split('T')[0] time = time.split('T')[1][:-1] year, date = date.split('-', 1) @@ -258,7 +246,6 @@ def parse_time(time): def print_in_order(dict, increasing=False): """ Prints items in q sorted by order given by sort - Parameters ---------- Q : PriorityQueue @@ -280,7 +267,6 @@ def find_match(open_or_closed, list_of_dictionaries, keyword, user, comment, number, branch, label): """Helper function that finds all matches in the given dictionary that fits the specified parameters. - Parameters ---------- open_or_closed : boolean @@ -297,7 +283,6 @@ def find_match(open_or_closed, list_of_dictionaries, Search by PR number branch : string Search by PR branch - Returns ---------- appeared_before : boolean @@ -351,9 +336,7 @@ def find_match(open_or_closed, list_of_dictionaries, def search(keyword, user, comment, number, branch, opened_or_closed, label, sort): """Searches open and closed pull request comments for specified keyword. - Opens pull-requests.toml file in .git folder to fetch pull requests. - Parameters ---------- keyword : string @@ -396,106 +379,6 @@ def search(keyword, user, comment, number, f"pull requests with 'git hub sync' and try again.") -def find_pr_info(pr, repo, token, open_or_closed): - """Helper function that finds all the information we want to record from the - pull requests from the API and converts it to toml format syntax - - Parameters - ---------- - pr : object - PR dictionary - repo : object - github object of the repo - token : string - authentication token for user - open_or_closed : string - if the PR is open or closed - """ - to_write = "" - url = f'https://api.github.com/repos/{username}/{repo}/pulls/{pr.number}' - request = Request(url) - request.add_header('Authorization', 'token %s' % token) - response = urlopen(request) - response = json.loads(response.read().decode('utf-8')) - issue_url = response.get('issue_url') - labels = "" - issue_request = Request(issue_url) - issue_response = urlopen(issue_request) - issue_response = json.loads(issue_response.read().decode('utf-8')) - labels_dict = issue_response.get('labels') - comment_url = response.get('comments_url') - comment_request = Request(comment_url) - comment_response = urlopen(comment_request) - comment_dict = json.loads(comment_response.read().decode('utf-8')) - - reviews = [reviewer.get('login') for reviewer in - list(response.get('requested_reviewers'))] - assignees = [assignee.get('login') - for assignee in list(response.get('assignees'))] - labels = [label.get('name') for label in list(labels_dict)] - comment_dates = [c.get('updated_at') for c in list(comment_dict)] - if(pr.number % 5 == 0): - print("Syncing PR ", pr.number) - to_write = f""" - ['{open_or_closed} pull requests'.{pr.number}]\n - title = "{repr(pr.title)}" \n - url = "{pr.html_url}" \n - body = "{repr(pr.body)}" \n - user = "{pr.user.login}" \n - branch = "{pr.head.ref}" \n - mergeable = "{pr.mergeable}" \n - comment = "{repr(pr.title)}" \n - commits = "{repr(pr.commits)}" \n - modified = "{pr.updated_at}" \n - created_at = "{pr.created_at}" \n - updated_at = "{pr.updated_at}" \n - closed_at = "{pr.closed_at}" \n - merged_at = "{pr.merged_at}" \n - reviews = {repr(reviews)} \n - assignees = {assignees} \n - milestone = "{pr.milestone.title if pr.milestone else ""}"\n - labels = {labels}\n - self_comment = "{any([c.user.login == pr.user.login - for c in list(comment_dict)])}"\n - comment_dates = {comment_dates}\n - most_recent = '{max(comment_dates) if comment_dates else ""}'\n - comment_content = {repr([c.body for c in list(comment_dict)])}\n - comment_count = '{len(list(comment_dict))}'\n - """ - return textwrap.dedent(to_write) - - -def find_issue_info(issue, token): - """Helper function that finds all the information - we want to record from the issues - from the API and converts it to toml format syntax. - - Parameters - ---------- - issue : object - github object that is an issue - token : string - authentication token for user - """ - - comment_dict = issue.get_comments() - comment_dates = [c.updated_at for c in list(comment_dict)] - comment_content = [c.body for c in list(comment_dict)] - to_write = f""" - ['issues'.{issue.number}]\n - title = "{issue.title}"\n - number = "{issue.number}"\n - body = '''{repr(issue.body)}'''\n - self_comment = "{any([c.user.login == issue.user.login - for c in list(comment_dict)])}"\n - comment_dates = "{comment_dates}"\n - most_recent = '{max(comment_dates) if comment_dates else ""}'\n - comment_content = {repr(comment_content)}\n - comment_count = '{len(list(comment_dict))}'\n - """ - return textwrap.dedent(to_write) - - def sync(): """Updates and saves pull-requests in pull-requests.toml in the .git folder. @@ -506,44 +389,333 @@ def sync(): # saves pr into toml file try: g = github.Github(token) + + except github.BadCredentialsException as e: + print(e) + click.echo("The authentification token is not valid.") + sys.exit(1) + try: path_git = path_to_git() path_github = pjoin(path_git, 'git-hub') path_prs = path_to_toml() + f = open(path_prs, "r") + pr_dict = toml.load(f) + if not pr_dict: + if not os.path.isdir(path_github): + os.makedirs(path_github) + f = open(path_prs, "w") + sync_helper(f, g, username, repo, token, {}) + else: + print('already synced before') + f = open(path_prs, "w") + sync_helper(f, g, username, repo, token, pr_dict) + + except FileNotFoundError as e: + print("haven't synced before") if not os.path.isdir(path_github): os.makedirs(path_github) f = open(path_prs, "w") - to_write = "['open pull requests']\n" - except github.BadCredentialsException as e: - print(e) - click.echo("The authentification token is not valid.") - sys.exit(1) + sync_helper(f, g, username, repo, token, {}) + + +def sync_helper(f, g, username, repo, token, pr_dict): + """Retrieves the information from the repo and saves it in file f. + Parameters + ---------- + f: file + The file to write to. + g: Github object + The github object from Pygithub. + username: string + Username of the user. + repo: string + repo name. + token: string + token used to authenticate Github object. + pr_dict: dictionary + Stores all of the information to put into f. + """ + if not pr_dict: + pr_dict = {'open pull requests': {}, 'closed pull requests': {}, + 'issues': {}, 'updated info': {}} + last_updated = datetime.min + pr_last_synced = 0 + last_category = None + last_issue_date = None + else: + last_updated = datetime.strptime(pr_dict['updated info']['last_sync'], + "%Y-%m-%d %H:%M:%S.%f") + pr_last_synced = pr_dict['updated info']['pr_last_synced'] + if pr_last_synced: + print("didn't get all the info") + last_issue_date = datetime.strptime(pr_last_synced, + "%Y-%m-%d %H:%M:%S") + else: + last_issue_date = None + last_category = pr_dict['updated info']['category'] + + g_repo = g.get_user(username).get_repo(repo) + open_prs = g_repo.get_pulls("open", sort="updated", direction="desc") + closed_prs = g_repo.get_pulls("closed", sort="updated", direction="desc") + issues = g_repo.get_issues(state="all", sort="updated", direction="desc") - # creates dictionaries in toml format as we scan though pull requests - github_repo = g.get_user(username).get_repo(repo) - open_prs = g.get_user(username).get_repo(repo).get_pulls("open") for pr in open_prs: - to_write = to_write + find_pr_info(pr, github_repo, token, "open") - to_write = to_write[:-1]+'\n' - to_write = to_write + "['closed pull requests'] \n" - closed_prs = g.get_user(username).get_repo(repo).get_pulls("closed") + if g.rate_limiting[0] / g.rate_limiting[1] < .2: + print(f"Over 80 percent of api calls used." + f" Sync later to get rest of the info.") + pr_dict['updated info'] = updated_info(pr.updated_at, "open") + toml.dump(pr_dict, f) + return + if pr.updated_at > last_updated: + print("updating new open " + str(pr.number)) + open_str = 'open pull requests' + pr_dict[open_str][str(pr.number)] = get_pr_info(pr, g_repo) + # if updates to open -->remove from closed + if str(pr.number) in pr_dict['closed pull requests']: + pr_dict['closed pull requests'].pop(str(pr.number)) + else: + print("passed updated date: open") + break + for pr in closed_prs: - to_write = to_write + find_pr_info(pr, github_repo, token, "closed") + if g.rate_limiting[0] / g.rate_limiting[1] < .2: + print(f"Over 80 percent of api calls used." + f" Sync later to get rest of the info.") + pr_dict['updated info'] = updated_info(pr.updated_at, "closed") + toml.dump(pr_dict, f) + return + # if newer updated version then pull all info + if pr.updated_at > last_updated: + print("updating new closed " + str(pr.number)) + closed_str = 'closed pull requests' + pr_dict[closed_str][str(pr.number)] = get_pr_info(pr, g_repo) + if str(pr.number) in pr_dict['open pull requests']: + pr_dict['open pull requests'].pop(str(pr.number)) + else: + print("passed updated_date closed") + break - issues = g.get_user(username).get_repo(repo).get_issues() - to_write = to_write + "['issues'] \n" for issue in issues: - if f"pull requests'.{issue.number}" not in to_write: - to_write = to_write + find_issue_info(issue, token) - # converts it to toml and stores in file - toml_string = toml.loads(to_write) - toml.dump(toml_string, f) + if g.rate_limiting[0] / g.rate_limiting[1] < .2: + print(f"Over 80 percent of api calls used." + f" Sync later to get rest of the info.") + pr_dict['updated info'] = updated_info(issue.updated_at, "issue") + toml.dump(pr_dict, f) + return + if issue.updated_at <= last_updated: + break + if (str(issue.number) not in pr_dict['open pull requests'] and + str(issue.number) not in pr_dict['closed pull requests'] and + issue.state == 'open'): + print("updating new issue " + str(issue.number)) + pr_dict['issues'][str(issue.number)] = get_issue_info(issue) + if issue.state == 'closed' and str(issue.number) in pr_dict['issues']: + print("closed issue") + pr_dict['issues'].pop(str(issue.number)) + + # get prs didn't pull down before + list_issues = [open_prs, closed_prs, issues] + pr_dict = get_older_info(g, g_repo, list_issues, + last_category, last_issue_date, pr_dict) + + toml.dump(pr_dict, f) + + +def get_older_info(g, g_repo, list_issues, + category, last_issue_date, pr_dict): + '''Retrieves old prs that weren't retrieved in the last sync. + Parameters + ---------- + g: Github object + Github object from Pygithub + g_repo: Github repo + Repo from g + list_issues: list + list of all issues from g + category: string + first category that has unretrieved info + last_issue_date: string + the date of the last issue retrieved + pr_dict: dictionary + dictionary of all pr info + Returns + ------- + pr_dict: dictionary + dictionary of all pr info + ''' + if not category: + pr_dict['updated info'] = updated_info(None, None) + return pr_dict + + open_prs, closed_prs, issues = list_issues + + if category == "open": + for pr in open_prs: + if g.rate_limiting[0] / g.rate_limiting[1] < .2: + print(f"Over 80 percent of api calls used." + f" Sync later to get rest of the info.") + pr_dict['updated info'] = updated_info(pr.updated_at, "open") + return pr_dict + # date is before last synced pr + if pr.updated_at <= last_issue_date: + print("updating old open " + str(pr.number)) + open_str = 'open pull requests' + num = str(pr.number) + pr_dict[open_str][num] = get_pr_info(pr, g_repo) + # if updates to open -->remove from closed + if str(pr.number) in pr_dict['closed pull requests']: + pr_dict['closed pull requests'].pop(str(pr.number)) + category = "closed" + last_issue_date = datetime.max + + if category == "closed": + for pr in closed_prs: + if g.rate_limiting[0] / g.rate_limiting[1] < .2: + print(f"Over 80 percent of api calls used." + f" Sync later to get rest of the info.") + pr_dict['updated info'] = updated_info(pr.updated_at, "closed") + return pr_dict + if pr.updated_at <= last_issue_date: + print("updating old closed " + str(pr.number)) + closed_str = 'closed pull requests' + num = str(pr.number) + pr_dict[closed_str][num] = get_pr_info(pr, g_repo) + if str(pr.number) in pr_dict['open pull requests']: + pr_dict['open pull requests'].pop(str(pr.number)) + category = "issues" + last_issue_date = datetime.max + + if category == "issues": + for issue in issues: + if g.rate_limiting[0] / g.rate_limiting[1] < .2: + print(f"Over 80 percent of api calls used." + f" Sync later to get rest of the info.") + pr_dict['updated info'] = updated_info(issue.updated_at, + "issue") + return pr_dict + + if (issue.updated_at <= last_issue_date and + str(issue.number) not in pr_dict['open pull requests'] and + str(issue.number) not in pr_dict['closed pull requests'] + and issue.state == 'open'): + print("updating old issue " + str(issue.number)) + pr_dict['issues'][str(issue.number)] = get_issue_info(issue) + if (issue.state == 'closed' and + str(issue.number) in pr_dict['issues']): + pr_dict['issues'].pop(str(issue.number)) + + pr_dict['updated info'] = updated_info(None, None) + return pr_dict + + +def get_pr_info(pr, repo): + '''create a dictionary of info about the pr. + Parameters + ---------- + pr: string + number of pr. + repo: github repo + repo from Github object. + Returns + ------- + pr_info: dictionary + contains information about the pr. + ''' + comment_dict = pr.get_comments() + comment_dates = [str(c.updated_at) for c in list(comment_dict)] + reviews = [reviewer.user for reviewer in + list(pr.get_review_comments())] + labels_dict = repo.get_issue(pr.number).labels + labels = [label.name for label in list(labels_dict)] + + pr_info = {} + pr_info['title'] = repr(pr.title) + pr_info['url'] = pr.html_url + pr_info['body'] = repr(pr.body) + pr_info['user'] = pr.user.login + pr_info['branch'] = pr.head.ref + pr_info['mergeable'] = pr.mergeable + pr_info['comment'] = repr(pr.title) + pr_info['commits'] = repr(pr.commits) + pr_info['modified'] = str(pr.updated_at) + pr_info['created_at'] = str(pr.created_at) + pr_info['updated_at'] = str(pr.updated_at) + pr_info['closed_at'] = str(pr.closed_at) + pr_info['merged_at'] = str(pr.merged_at) + pr_info['reviews'] = repr(reviews) + pr_info['assignees'] = [assignee.login + for assignee in list(pr.assignees)] + pr_info['milestone'] = pr.milestone.title if pr.milestone else "" + pr_info['labels'] = labels + pr_info['self_comment'] = any([c.user.login == pr.user.login + for c in list(comment_dict)]) + pr_info['comment_dates'] = comment_dates + pr_info['most_recent'] = str(max(comment_dates)) if comment_dates else "" + pr_info['comment_content'] = [repr(c.body) for c in list(comment_dict)] + pr_info['comment_count'] = len(list(comment_dict)) + return pr_info + + +def get_issue_info(issue): + '''Creates dictionary of info about issue. + Parameters + ---------- + issue: Github repo + repo from Github object + Returns + ------- + issue_info: dictionary + dictionary of issue information + ''' + comment_dict = issue.get_comments() + comment_dates = [str(c.updated_at) for c in list(comment_dict)] + comment_content = [repr(c.body) for c in list(comment_dict)] + + issue_info = {} + issue_info['title'] = issue.title + issue_info['number'] = issue.number + issue_info['body'] = repr(issue.body) + issue_info['self_comment'] = any([c.user.login == issue.user.login + for c in list(comment_dict)]) + issue_info['comment_dates'] = comment_dates + issue_info['updated_at'] = str(issue.updated_at) + issue_info['most_recent'] = (str(max(comment_dates)) + if comment_dates else "") + issue_info['comment_content'] = comment_content + issue_info['comment_count'] = len(list(comment_dict)) + return issue_info + + +def updated_info(issue_updated_date, category): + '''Stores information about current sync for later syncs. + Parameters + ---------- + issue_updated_date: string + date of the last issue updated + category: string + last category to be updated + Returns + ------- + updated: dictionary + dictionary of stored info about sync + ''' + updated = {} + updated['last_sync'] = str(datetime.utcnow()) + if category: + updated['pr_last_synced'] = str(issue_updated_date) + updated['category'] = str(category) + else: + updated['pr_last_synced'] = False + updated['category'] = False + return updated def render(): try: plot_pr.execute() except ImportError: - print("Matplotlib not installed, cannot render image. Continuing without image.") + print("Matplotlib not installed, cannot render image.") + print("Continuing without image.") app.main() @@ -574,7 +746,10 @@ def cli(): @click.option('--label', '-l', default="", help="search PR by label") def hub(command, args, user, comment, number, branch, sort, open, label): if command == "pr": - pr_num = int(args[0]) + if args[0].isdigit(): + pr_num = int(args[0]) + else: + pr_num = int(args[0][-1]) pr(pr_num) elif command == "push": diff --git a/requirements.txt b/requirements.txt index 7bbc38b..a5cca19 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,4 @@ pyyaml click toml requests -<<<<<<< HEAD -jinja2 -======= ->>>>>>> 3231212d9922ba239f446f1aaa9503a4a6bc0831 +jinja2 \ No newline at end of file