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 local_config( config, push_to_origin="", toggle_fixes_message=False, toggle_username_branches=None, ): """Setting configuration options per repo name""" if push_to_origin: try: before = load_config(config.configfile, "push_to_origin") print(f"push_to_origin before: {before}") except KeyError: print("push_to_origin before: not set") new_value = json.loads(push_to_origin) update_config(config.configfile, push_to_origin=new_value) print(f"push_to_origin after: {new_value}") if toggle_fixes_message: try: before = load_config(config.configfile, "fixes_message") print(f"toggle_fixes_message before: {before}") except KeyError: print("toggle_fixes_message before: not set") before = True # the default new_value = not before update_config(config.configfile, fixes_message=new_value) print(f"fixes_message after: {new_value}") if toggle_username_branches is not None: try: before = load_config(config.configfile, "username_branches") print(f"username_branches before: {before}") except KeyError: print("username_branches before: not set") before = False # the default new_value = not before update_config(config.configfile, username_branches=new_value) print(f"username_branches after: {new_value}")
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 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 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 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