def login(config, api_key=""): """Store your Bugzilla API Key""" if not api_key: info_out("If you don't have an API Key, go to:\n" "https://bugzilla.mozilla.org/userprefs.cgi?tab=apikey\n") api_key = getpass.getpass("API Key: ") # Before we store it, let's test it. url = urllib.parse.urljoin(config.bugzilla_url, "/rest/whoami") assert url.startswith("https://"), url response = requests.get(url, params={"api_key": api_key}) if response.status_code == 200: if response.json().get("error"): error_out(f"Failed - {response.json()}") else: update( config.configfile, { "BUGZILLA": { "bugzilla_url": config.bugzilla_url, "api_key": api_key, # "login": login, } }, ) success_out("Yay! It worked!") else: error_out(f"Failed - {response.status_code} ({response.json()})")
def token(config, token): """Store and fetch a GitHub access token""" if not token: info_out("To generate a personal API token, go to:\n\n\t" "https://github.com/settings/tokens\n\n" "To read more about it, go to:\n\n\t" "https://help.github.com/articles/creating-an-access" "-token-for-command-line-use/\n\n" 'Remember to enable "repo" in the scopes.') token = getpass.getpass("GitHub API Token: ").strip() url = urllib.parse.urljoin(config.github_url, "/user") assert url.startswith("https://"), url response = requests.get(url, headers={"Authorization": f"token {token}"}) if response.status_code == 200: update( config.configfile, { "GITHUB": { "github_url": config.github_url, "token": token, "login": response.json()["login"], } }, ) name = response.json()["name"] or response.json()["login"] success_out(f"Hi! {name}") else: error_out(f"Failed - {response.status_code} ({response.content})")
def logout(config): """Remove and forget your Bugzilla credentials""" state = read(config.configfile) if state.get("BUGZILLA"): remove(config.configfile, "BUGZILLA") success_out("Forgotten") else: error_out("No stored Bugzilla credentials")
def burn(config): """Remove and forget your GitHub credentials""" state = read(config.configfile) if state.get("GITHUB"): remove(config.configfile, "GITHUB") success_out("Forgotten") else: error_out("No stored GitHub credentials")
def start(config, bugnumber=""): """Create a new topic branch.""" repo = config.repo try: username_branches = load_config(config.configfile, "username_branches") except KeyError: username_branches = False if bugnumber: summary, bugnumber, url = get_summary(config, bugnumber) else: url = None summary = None if summary: summary = input('Summary ["{}"]: '.format(summary)).strip() or summary else: summary = input("Summary: ").strip() branch_name = "" if username_branches: branch_name += f"{os.getlogin()}-" if bugnumber: if is_github({"bugnumber": bugnumber, "url": url}): branch_name += f"{bugnumber}-" else: branch_name += f"{bugnumber}-" def clean_branch_name(string): string = re.sub(r"\s+", " ", string) for each in " |": string = string.replace(each, "-") for each in ("->", "=>"): string = string.replace(each, "-") for each in "@%^&:'\"/(),[]{}!.?`$<>#*;=": string = string.replace(each, "") string = re.sub("-+", "-", string) string = string.strip("-") return string.lower().strip() branch_name += clean_branch_name(summary) if not branch_name: error_out("Must provide a branch name") # Check that the branch doesn't already exist found = list(find(repo, branch_name, exact=True)) if found: error_out("There is already a branch called {!r}".format( found[0].name)) new_branch = repo.create_head(branch_name) new_branch.checkout() if config.verbose: click.echo("Checkout out new branch: {}".format(branch_name)) save(config.configfile, summary, branch_name, bugnumber=bugnumber, url=url)
def test(config, bugnumber): """Test your saved Bugzilla API Key.""" state = read(config.configfile) credentials = state.get("BUGZILLA") if not credentials: error_out("No API Key saved. Run: gg bugzilla login") if config.verbose: info_out(f"Using: {credentials['bugzilla_url']}") if bugnumber: summary, _ = get_summary(config, bugnumber) if summary: info_out("It worked!") success_out(summary) else: error_out("Unable to fetch") else: url = urllib.parse.urljoin(credentials["bugzilla_url"], "/rest/whoami") assert url.startswith("https://"), url response = requests.get(url, params={"api_key": credentials["api_key"]}) if response.status_code == 200: if response.json().get("error"): error_out(f"Failed! - {response.json()}") else: success_out(json.dumps(response.json(), indent=2)) else: error_out( f"Failed to query - {response.status_code} ({response.json()})" )
def mastermerge(config): """Merge the origin_name/default_branch into the the current branch""" repo = config.repo state = read(config.configfile) origin_name = state.get("ORIGIN_NAME", "origin") default_branch = get_default_branch(repo, origin_name) active_branch = repo.active_branch if active_branch.name == default_branch: error_out(f"You're already on the {default_branch} branch.") active_branch_name = active_branch.name if repo.is_dirty(): error_out( 'Repo is "dirty". ({})'.format( ", ".join([repr(x.b_path) for x in repo.index.diff(None)]) ) ) upstream_remote = None for remote in repo.remotes: if remote.name == origin_name: upstream_remote = remote break if not upstream_remote: error_out(f"No remote called {origin_name!r} found") repo.heads[default_branch].checkout() repo.remotes[origin_name].pull(default_branch) repo.heads[active_branch_name].checkout() repo.git.merge(default_branch) success_out(f"Merged against {origin_name}/{default_branch}")
def test(config, issue_url): """Test your saved GitHub API Token.""" state = read(config.configfile) credentials = state.get("GITHUB") if not credentials: error_out("No credentials saved. Run: gg github token") if config.verbose: info_out(f"Using: {credentials['github_url']}") if issue_url: github_url_regex = re.compile( r"https://github.com/([^/]+)/([^/]+)/issues/(\d+)") org, repo, number = github_url_regex.search(issue_url).groups() title, _ = get_title(config, org, repo, number) if title: info_out("It worked!") success_out(title) else: error_out("Unable to fetch") else: url = urllib.parse.urljoin(credentials["github_url"], "/user") assert url.startswith("https://"), url response = requests.get( url, headers={"Authorization": f"token {credentials['token']}"}) if response.status_code == 200: success_out(json.dumps(response.json(), indent=2)) else: error_out( f"Failed to query - {response.status_code} ({response.json()})" )
def rebase(config): """Rebase the current branch against $origin/$branch""" repo = config.repo state = read(config.configfile) default_branch = state.get("DEFAULT_BRANCH", "master") active_branch = repo.active_branch if active_branch.name == default_branch: error_out(f"You're already on the {default_branch} branch.") active_branch_name = active_branch.name if repo.is_dirty(): error_out('Repo is "dirty". ({})'.format(", ".join( [repr(x.b_path) for x in repo.index.diff(None)]))) origin_name = state.get("ORIGIN_NAME", "origin") upstream_remote = None for remote in repo.remotes: if remote.name == origin_name: upstream_remote = remote break if not upstream_remote: error_out("No remote called {!r} found".format(origin_name)) repo.heads[default_branch].checkout() repo.remotes[origin_name].pull(default_branch) repo.heads[active_branch_name].checkout() print(repo.git.rebase(default_branch)) success_out(f"Rebased against {origin_name}/{default_branch}") info_out(f"If you want to start interactive rebase " f"run:\n\n\tgit rebase -i {default_branch}\n")
def branches(config, yes=False, searchstring="", cutoff=DEFAULT_CUTOFF): """List all branches. And if exactly 1 found, offer to check it out.""" repo = config.repo try: branches_ = list(find(repo, searchstring)) except InvalidRemoteName as exception: remote_search_name = searchstring.split(":")[0] if remote_search_name in [x.name for x in repo.remotes]: error_out(f"Invalid remote name {exception!r}") (repo_name, ) = [ x.url.split("/")[-1].split(".git")[0] for x in repo.remotes if x.name == "origin" ] # Add it and start again remote_url = f"https://github.com/{remote_search_name}/{repo_name}.git" if not click.confirm( f"Add remote {remote_search_name!r} ({remote_url})", default=True): error_out("Unable to find or create remote") repo.create_remote(remote_search_name, remote_url) branches_ = list(find(repo, searchstring)) if branches_: merged = get_merged_branches(repo) info_out("Found existing branches...") print_list(branches_, merged, cutoff=cutoff) if len(branches_) == 1 and searchstring: # If the found branch is the current one, error active_branch = repo.active_branch if active_branch == branches_[0]: error_out(f"You're already on {branches_[0].name!r}") branch_name = branches_[0].name if len(branch_name) > 50: branch_name = branch_name[:47] + "…" if not yes: check_it_out = (input(f"Check out {branch_name!r}? [Y/n] "). lower().strip() != "n") if yes or check_it_out: branches_[0].checkout() elif searchstring: warning_out(f"Found no branches matching {searchstring!r}.") else: warning_out("Found no branches.")
def merge(config): """Merge the current branch into $default_branch.""" repo = config.repo state = read(config.configfile) default_branch = state.get("DEFAULT_BRANCH", "master") active_branch = repo.active_branch if active_branch.name == default_branch: error_out(f"You're already on the {default_branch} branch.") if repo.is_dirty(): error_out('Repo is "dirty". ({})'.format(", ".join( [repr(x.b_path) for x in repo.index.diff(None)]))) branch_name = active_branch.name origin_name = state.get("ORIGIN_NAME", "origin") upstream_remote = None for remote in repo.remotes: if remote.name == origin_name: upstream_remote = remote break if not upstream_remote: error_out(f"No remote called {origin_name!r} found") repo.heads[default_branch].checkout() upstream_remote.pull(repo.heads[default_branch]) repo.git.merge(branch_name) repo.git.branch("-d", branch_name) success_out("Branch {!r} deleted.".format(branch_name)) info_out("NOW, you might want to run:\n") info_out(f"git push {origin_name} {default_branch}\n\n") push_for_you = input("Run that push? [Y/n] ").lower().strip() != "n" if push_for_you: upstream_remote.push(default_branch) success_out( f"Current {default_branch} pushed to {upstream_remote.name}")
def test_error_out(capsys): with pytest.raises(click.Abort): utils.error_out("Blarg") out, err = capsys.readouterr() assert not err assert out == "Blarg\n"
def get_summary(config, bugnumber): """return a summary for this bug/issue. If it can't be found, return None.""" bugzilla_url_regex = re.compile( re.escape("https://bugzilla.mozilla.org/show_bug.cgi?id=") + r"(\d+)$") # The user could have pasted in a bugzilla ID or a bugzilla URL if bugzilla_url_regex.search(bugnumber.split("#")[0]): # that's easy then! (bugzilla_id, ) = bugzilla_url_regex.search( bugnumber.split("#")[0]).groups() bugzilla_id = int(bugzilla_id) summary, url = bugzilla.get_summary(config, bugzilla_id) return summary, bugzilla_id, url # The user could have pasted in a GitHub issue URL github_url_regex = re.compile( r"https://github.com/([^/]+)/([^/]+)/issues/(\d+)") if github_url_regex.search(bugnumber.split("#")[0]): # that's also easy ( org, repo, id_, ) = github_url_regex.search(bugnumber.split("#")[0]).groups() id_ = int(id_) title, url = github.get_title(config, org, repo, id_) if title: return title.strip(), id_, url else: return None, None, None # If it's a number it can be either a github issue or a bugzilla bug if bugnumber.isdigit(): # try both and see if one of them turns up something interesting repo = config.repo state = read(config.configfile) fork_name = state.get("FORK_NAME", getpass.getuser()) if config.verbose: info_out("Using fork name: {}".format(fork_name)) candidates = [] # Looping over the remotes, let's figure out which one # is the one that has issues. Let's try every one that isn't # your fork remote. for origin in repo.remotes: if origin.name == fork_name: continue url = origin.url org, repo = parse_remote_url(origin.url) github_title, github_url = github.get_title( config, org, repo, int(bugnumber)) if github_title: candidates.append((github_title, int(bugnumber), github_url)) bugzilla_summary, bugzilla_url = bugzilla.get_summary( config, bugnumber) if bugzilla_summary: candidates.append((bugzilla_summary, int(bugnumber), bugzilla_url)) if len(candidates) > 1: info_out("Input is ambiguous. Multiple possibilities found. " "Please re-run with the full URL:") for title, _, url in candidates: info_out("\t{}".format(url)) info_out("\t{}\n".format(title)) error_out("Awaiting your choice") elif len(candidates) == 1: return candidates[0] else: error_out("ID could not be found on GitHub or Bugzilla") raise Exception(bugnumber) return bugnumber, None, None
def commit(config, no_verify, yes): """Commit the current branch with all files.""" repo = config.repo state = read(config.configfile) origin_name = state.get("ORIGIN_NAME", "origin") default_branch = get_default_branch(repo, origin_name) active_branch = repo.active_branch if active_branch.name == default_branch: error_out(f"Can't commit when on the {default_branch} branch. " f"You really ought to do work in branches.") now = time.time() def count_files_in_directory(directory): count = 0 for root, _, files in os.walk(directory): # We COULD crosscheck these files against the .gitignore # if we ever felt overachievious. count += len(files) return count # First group all untracked files by root folder all_untracked_files = {} for path in repo.untracked_files: root = path.split(os.path.sep)[0] if root not in all_untracked_files: all_untracked_files[root] = { "files": [], "total_count": count_files_in_directory(root), } all_untracked_files[root]["files"].append(path) # Now filter this based on it being single files or a bunch untracked_files = {} for root, info in all_untracked_files.items(): for path in info["files"]: age = now - os.stat(path).st_mtime # If there's fewer untracked files in its directory, suggest # the directory instead. if info["total_count"] == 1: path = root if path in untracked_files: if age < untracked_files[path]: # youngest file in that directory untracked_files[path] = age else: untracked_files[path] = age if untracked_files: ordered = sorted(untracked_files.items(), key=lambda x: x[1], reverse=True) info_out("NOTE! There are untracked files:") for path, age in ordered: if os.path.isdir(path): path = path + "/" print("\t", path.ljust(60), humanize_seconds(age), "old") # But only put up this input question if one the files is # younger than 12 hours. young_ones = [x for x in untracked_files.values() if x < 60 * 60 * 12] if young_ones: ignore = input("Ignore untracked files? [Y/n] ").lower().strip() if ignore.lower().strip() == "n": error_out("\n\tLeaving it up to you to figure out what to do " "with those untracked files.") return 1 print("") state = read(config.configfile) try: push_to_origin = load_config(config.configfile, "push_to_origin") except KeyError: push_to_origin = False try: fixes_message = load_config(config.configfile, "fixes_message") except KeyError: fixes_message = True try: data = load(config.configfile, active_branch.name) except KeyError: error_out("You're in a branch that was not created with gg.\n" "No branch information available.") print("Commit message: (type a new one if you want to override)") msg = data["description"] if data.get("bugnumber"): if is_bugzilla(data): msg = "bug {} - {}".format(data["bugnumber"], data["description"]) msg = input('"{}" '.format(msg)).strip() or msg elif is_github(data): msg = input('"{}" '.format(msg)).strip() or msg if fixes_message: msg += "\n\nPart of #{}".format(data["bugnumber"]) if data["bugnumber"] and fixes_message: question = 'Add the "fixes" mention? [N/y] ' fixes = input(question).lower().strip() if fixes in ("y", "yes") or yes: if is_bugzilla(data): msg = "fixes " + msg elif is_github(data): msg = msg.replace("Part of ", "Fixes ") else: raise NotImplementedError # Now we're going to do the equivalent of `git commit -a -m "..."` index = repo.index files_added = [] files_removed = [] for x in repo.index.diff(None): if x.deleted_file: files_removed.append(x.b_path) else: files_added.append(x.b_path) files_new = [] for x in repo.index.diff(repo.head.commit): files_new.append(x.b_path) proceed = True if not (files_added or files_removed or files_new): info_out("No files to add or remove.") proceed = False if input("Proceed anyway? [Y/n] ").lower().strip() == "n": proceed = True if proceed: if not repo.is_dirty(): error_out("Branch is not dirty. There is nothing to commit.") if files_added: index.add(files_added) if files_removed: index.remove(files_removed) try: # Do it like this (instead of `repo.git.commit(msg)`) # so that git signing works. commit = repo.git.commit(["-m", msg]) except git.exc.HookExecutionError as exception: if not no_verify: info_out("Commit hook failed ({}, exit code {})".format( exception.command, exception.status)) if exception.stdout: error_out(exception.stdout) elif exception.stderr: error_out(exception.stderr) else: error_out("Commit hook failed.") else: commit = index.commit(msg, skip_hooks=True) success_out("Commit created {}".format(commit)) if not state.get("FORK_NAME"): info_out( "Can't help you push the commit. Please run: gg config --help") return 0 if push_to_origin: try: repo.remotes[origin_name] except IndexError: error_out(f"There is no remote called {origin_name!r}") else: try: repo.remotes[state["FORK_NAME"]] except IndexError: error_out(f"There is no remote called {state['FORK_NAME']!r}") remote_name = origin_name if push_to_origin else state["FORK_NAME"] if yes: push_for_you = "yes" else: push_for_you = input( f"Push branch to {remote_name!r}? [Y/n] ").lower().strip() if push_for_you not in ("n", "no"): push_output = repo.git.push("--set-upstream", remote_name, active_branch.name) print(push_output) else: # If you don't want to push, then don't bother with GitHub # Pull Request stuff. return 0 if not state.get("GITHUB"): if config.verbose: info_out("Can't help create a GitHub Pull Request.\n" "Consider running: gg github --help") return 0 origin = repo.remotes[state.get("ORIGIN_NAME", "origin")] rest = re.split(r"github\.com[:/]", origin.url)[1] org, repo = rest.split(".git")[0].split("/", 1) # Search for an existing open pull request, and remind us of the link # to it. search = { "head": f"{remote_name}:{active_branch.name}", "state": "open", } for pull_request in github.find_pull_requests(config, org, repo, **search): print("Pull Request already created:") print("") print("\t", pull_request["html_url"]) print("") break else: # If no known Pull Request exists, make a link to create a new one. if remote_name == origin.name: github_url = "https://github.com/{}/{}/compare/{}...{}?expand=1".format( org, repo, default_branch, active_branch.name) else: github_url = ( "https://github.com/{}/{}/compare/{}:{}...{}:{}?expand=1". format( org, repo, org, default_branch, remote_name, active_branch.name, )) print("Now, to make a Pull Request, go to:") print("") success_out(github_url) print("(⌘-click to open URLs)") return 0
def push(config, force=False): """Create push the current branch.""" repo = config.repo state = read(config.configfile) default_branch = state.get("DEFAULT_BRANCH", "master") active_branch = repo.active_branch if active_branch.name == default_branch: error_out(f"Can't commit when on the {default_branch} branch. " "You really ought to do work in branches.") if not state.get("FORK_NAME"): info_out( "Can't help you push the commit. Please run: gg config --help") return 0 try: push_to_origin = load_config(config.configfile, "push_to_origin") except KeyError: push_to_origin = False try: repo.remotes[state["FORK_NAME"]] except IndexError: error_out("There is no remote called '{}'".format( state["FORK_NAME"])) origin_name = state.get("ORIGIN_NAME", "origin") destination = repo.remotes[ origin_name if push_to_origin else state["FORK_NAME"]] if force: (pushed, ) = destination.push(force=True) info_out(pushed.summary) else: (pushed, ) = destination.push() # print("PUSHED...") # for enum_name in [ # "DELETED", # "ERROR", # "FAST_FORWARD", # "NEW_HEAD", # "NEW_TAG", # "NO_MATCH", # "REMOTE_FAILURE", # ]: # print( # f"{enum_name}?:", pushed.flags & getattr(git.remote.PushInfo, enum_name) # ) if pushed.flags & git.remote.PushInfo.FORCED_UPDATE: success_out(f"Successfully force pushed to {destination}") elif (pushed.flags & git.remote.PushInfo.REJECTED or pushed.flags & git.remote.PushInfo.REMOTE_REJECTED): error_out('The push was rejected ("{}")'.format(pushed.summary), False) try_force_push = input("Try to force push? [Y/n] ").lower().strip() if try_force_push not in ("no", "n"): (pushed, ) = destination.push(force=True) info_out(pushed.summary) else: return 0 elif pushed.flags & git.remote.PushInfo.UP_TO_DATE: info_out(f"{destination} already up-to-date") else: success_out(f"Successfully pushed to {destination}") return 0
def cleanup(config, searchstring, force=False): """Deletes a found branch locally and remotely.""" repo = config.repo state = read(config.configfile) origin_name = state.get("ORIGIN_NAME", "origin") # default_branch = state.get("DEFAULT_BRANCH", "master") default_branch = get_default_branch(repo, origin_name) branches_ = list(find(repo, searchstring)) if not branches_: error_out("No branches found") elif len(branches_) > 1: error_out("More than one branch found.{}".format( "\n\t".join([""] + [x.name for x in branches_]))) assert len(branches_) == 1 branch_name = branches_[0].name active_branch = repo.active_branch if branch_name == active_branch.name: error_out("Can't clean up the current active branch.") # branch_name = active_branch.name upstream_remote = None fork_remote = None origin_name = state.get("ORIGIN_NAME", "origin") for remote in repo.remotes: if remote.name == origin_name: # remote.pull() upstream_remote = remote break if not upstream_remote: error_out("No remote called {!r} found".format(origin_name)) # Check out default branch repo.heads[default_branch].checkout() upstream_remote.pull(repo.heads[default_branch]) # Is this one of the merged branches?! # XXX I don't know how to do this "nativly" with GitPython. merged_branches = [ x.strip() for x in repo.git.branch("--merged").splitlines() if x.strip() and not x.strip().startswith("*") ] was_merged = branch_name in merged_branches certain = was_merged or force if not certain: # Need to ask the user. # XXX This is where we could get smart and compare this branch # with the default branch. certain = (input("Are you certain {} is actually merged? [Y/n] ". format(branch_name)).lower().strip() != "n") if not certain: return 1 if was_merged: repo.git.branch("-d", branch_name) else: repo.git.branch("-D", branch_name) fork_remote = None state = read(config.configfile) for remote in repo.remotes: if remote.name == state.get("FORK_NAME"): fork_remote = remote break if fork_remote: fork_remote.push(":" + branch_name) info_out("Remote branch on fork deleted too.")
def test_error_out_no_raise(capsys): utils.error_out("Blarg", False) out, err = capsys.readouterr() assert not err assert out == "Blarg\n"
def getback(config, force=False): """Goes back to the default branch, deletes the current branch locally and remotely.""" repo = config.repo state = read(config.configfile) origin_name = state.get("ORIGIN_NAME", "origin") default_branch = get_default_branch(repo, origin_name) active_branch = repo.active_branch if active_branch.name == default_branch: error_out(f"You're already on the {default_branch} branch.") if repo.is_dirty(): dirty_paths = ", ".join( [repr(x.b_path) for x in repo.index.diff(None)]) error_out(f'Repo is "dirty". ({dirty_paths})') branch_name = active_branch.name upstream_remote = None fork_remote = None for remote in repo.remotes: if remote.name == origin_name: # remote.pull() upstream_remote = remote break if not upstream_remote: error_out(f"No remote called {origin_name!r} found") # Check out default branch repo.heads[default_branch].checkout() upstream_remote.pull(repo.heads[default_branch]) # Is this one of the merged branches?! # XXX I don't know how to do this "natively" with GitPython. merged_branches = [ x.strip() for x in repo.git.branch("--merged").splitlines() if x.strip() and not x.strip().startswith("*") ] was_merged = branch_name in merged_branches certain = was_merged or force if not certain: # Need to ask the user. # XXX This is where we could get smart and compare this branch # with the default branch. certain = (input("Are you certain {} is actually merged? [Y/n] ". format(branch_name)).lower().strip() != "n") if not certain: return 1 if was_merged: repo.git.branch("-d", branch_name) else: repo.git.branch("-D", branch_name) try: push_to_origin = load_config(config.configfile, "push_to_origin") except KeyError: push_to_origin = False remote_name = origin_name if push_to_origin else state["FORK_NAME"] fork_remote = None for remote in repo.remotes: if remote.name == remote_name: fork_remote = remote break else: info_out(f"Never found the remote {remote_name}") if fork_remote: fork_remote.push(":" + branch_name) info_out("Remote branch on fork deleted too.")
def pr(config): """Find PRs based on the current branch""" repo = config.repo state = read(config.configfile) origin_name = state.get("ORIGIN_NAME", "origin") default_branch = get_default_branch(repo, origin_name) active_branch = repo.active_branch if active_branch.name == default_branch: error_out(f"You can't find PRs from the {default_branch} branch. ") state = read(config.configfile) try: push_to_origin = load_config(config.configfile, "push_to_origin") except KeyError: push_to_origin = False # try: # data = load(config.configfile, active_branch.name) # except KeyError: # error_out( # "You're in a branch that was not created with gg.\n" # "No branch information available." # ) if not state.get("GITHUB"): if config.verbose: info_out("Can't help create a GitHub Pull Request.\n" "Consider running: gg github --help") return 0 origin = repo.remotes[state.get("ORIGIN_NAME", "origin")] rest = re.split(r"github\.com[:/]", origin.url)[1] org, repo = rest.split(".git")[0].split("/", 1) # Search for an existing open pull request, and remind us of the link # to it. if push_to_origin: head = active_branch.name else: head = f"{state['FORK_NAME']}:{active_branch.name}" search = { "head": head, "state": "open", } for pull_request in github.find_pull_requests(config, org, repo, **search): print("Pull Request:") print("") print( "\t", pull_request["html_url"], " ", "(DRAFT)" if pull_request["draft"] else f"({pull_request['state'].upper()})", ) print("") full_pull_request = github.get_pull_request(config, org, repo, pull_request["number"]) # from pprint import pprint # pprint(full_pull_request) print("Mergeable?", full_pull_request.get("mergeable", "*not known*")) print("Updated", full_pull_request["updated_at"]) print("") break else: info_out("Sorry, can't find a PR") return 0