Ejemplo n.º 1
0
Archivo: github.py Proyecto: peterbe/gg
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})")
Ejemplo n.º 2
0
Archivo: github.py Proyecto: peterbe/gg
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()})"
            )
Ejemplo n.º 3
0
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()})")
Ejemplo n.º 4
0
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")
Ejemplo n.º 5
0
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()})"
            )
Ejemplo n.º 6
0
Archivo: github.py Proyecto: peterbe/gg
def get_pull_request(config, org, repo, number):
    base_url = GITHUB_URL
    headers = {}
    state = read(config.configfile)
    credentials = state.get("GITHUB")
    if credentials:
        headers["Authorization"] = f"token {credentials['token']}"
        if config.verbose:
            info_out(f'Using API token: {credentials["token"][:10] + "…"}')
    url = urllib.parse.urljoin(base_url, f"/repos/{org}/{repo}/pulls/{number}")
    if config.verbose:
        info_out(f"GitHub URL: {url}")
    assert url.startswith("https://"), url
    response = requests.get(url, headers=headers)
    if response.status_code == 200:
        return response.json()
Ejemplo n.º 7
0
def find(repo, searchstring, exact=False):
    # When you copy-to-clipboard from GitHub you get something like
    # 'peterbe:1545809-urllib3-1242' for example.
    # But first, it exists as a local branch, use that.
    if searchstring and ":" in searchstring:
        remote_name = searchstring.split(":")[0]
        for remote in repo.remotes:
            if remote.name == remote_name:
                # remote.pull()
                found_remote = remote
                break
        else:
            raise InvalidRemoteName(remote_name)

        for head in repo.heads:
            if exact:
                if searchstring.split(":", 1)[1].lower() == head.name.lower():
                    yield head
                    return
            else:
                if searchstring.split(":", 1)[1].lower() in head.name.lower():
                    yield head
                    return

        info_out(f"Fetching the latest from {found_remote}")
        for fetchinfo in found_remote.fetch():
            if fetchinfo.flags & git.remote.FetchInfo.HEAD_UPTODATE:
                # Most boring
                pass
            else:
                msg = "updated"
                if fetchinfo.flags & git.remote.FetchInfo.FORCED_UPDATE:
                    msg += " (force updated)"
                print(fetchinfo.ref, msg)

            if str(fetchinfo.ref) == searchstring.replace(":", "/", 1):
                yield fetchinfo.ref

    for head in repo.heads:
        if searchstring:
            if exact:
                if searchstring.lower() != head.name.lower():
                    continue
            else:
                if searchstring.lower() not in head.name.lower():
                    continue
        yield head
Ejemplo n.º 8
0
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.")
Ejemplo n.º 9
0
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}")
Ejemplo n.º 10
0
def config(config, fork_name="", origin_name="", default_branch=""):
    """Setting various configuration options"""
    state = read(config.configfile)
    if fork_name:
        update(config.configfile, {"FORK_NAME": fork_name})
        success_out(f"fork-name set to: {fork_name}")
    else:
        info_out(f"fork-name: {state['FORK_NAME']}")

    if origin_name:
        update(config.configfile, {"ORIGIN_NAME": origin_name})
        success_out(f"origin-name set to: {origin_name}")
    else:
        info_out(f"origin-name: {state.get('ORIGIN_NAME', '*not set*')}")

    if default_branch:
        update(config.configfile, {"DEFAULT_BRANCH": default_branch})
        success_out(f"default-branch set to: {default_branch}")
    else:
        info_out(f"default-branch: {state.get('DEFAULT_BRANCH', '*not set*')}")
Ejemplo n.º 11
0
Archivo: github.py Proyecto: peterbe/gg
def get_title(config, org, repo, number):
    base_url = GITHUB_URL
    headers = {}
    state = read(config.configfile)
    credentials = state.get("GITHUB")
    if credentials:
        base_url = state["GITHUB"]["github_url"]
        headers["Authorization"] = f"token {credentials['token']}"
        if config.verbose:
            info_out(f'Using API token: {credentials["token"][:10] + "…"}')
    url = urllib.parse.urljoin(base_url,
                               f"/repos/{org}/{repo}/issues/{number}")
    if config.verbose:
        info_out(f"GitHub URL: {url}")
    assert url.startswith("https://"), url
    response = requests.get(url, headers=headers)
    response.raise_for_status()
    if response.status_code == 200:
        data = response.json()
        return data["title"], data["html_url"]
    if config.verbose:
        info_out(f"GitHub Response: {response}")
    return None, None
Ejemplo n.º 12
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.")
Ejemplo n.º 13
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
Ejemplo n.º 14
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
Ejemplo n.º 15
0
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
Ejemplo n.º 16
0
def print_list(heads, merged_names, cutoff=10):
    def wrap(head):
        commit = head.commit
        return {
            "head": head,
            "info": {
                "date": commit.committed_datetime,
                "message": commit.message
            },
        }

    def format_age(dt):
        # This `dt` is timezone aware. So cheat, so we don't need to figure out
        # our timezone is.
        delta = datetime.datetime.utcnow().timestamp() - dt.timestamp()
        return str(datetime.timedelta(seconds=delta))

    def format_msg(message):
        message = message.strip().replace("\n", "\\n")
        if len(message) > 80:
            return message[:76] + "…"
        return message

    wrapped = sorted(
        [wrap(head) for head in heads],
        key=lambda x: x["info"].get("date"),
        reverse=True,
    )

    for each in wrapped[:cutoff]:
        info_out("".center(80, "-"))
        success_out(each["head"].name + (
            each["head"].name in merged_names and " (MERGED ALREADY)" or ""))
        if each.get("error"):
            info_out(f"\tError getting ref log ({each['error']!r})")
        info_out("\t" + each["info"]["date"].isoformat())
        info_out("\t" + format_age(each["info"]["date"]))
        info_out("\t" +
                 format_msg(each["info"].get("message", "*no commit yet*")))
        info_out("")

    if len(heads) > cutoff:
        warning_out(
            f"Note! Found total of {len(heads)} but only showing {cutoff} most recent."
        )
Ejemplo n.º 17
0
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.")
Ejemplo n.º 18
0
Archivo: gg_pr.py Proyecto: peterbe/gg
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