5

I looked at a few references but I am still having problems:

I want to clone a remote repo, create a new branch, and push the new branch back to remote using GitPython.

This seems to work:

import git
import subprocess

nm_brnch = 'new_branch'

# Clone    
repo_url = r'my_remote.git'
repo = git.Repo.clone_from(repo_url, dnm_wrk, branch=r'some_branch')

# Create new branch
git = repo.git
git.checkout('HEAD', b=nm_brnch)

# Push new branch to remote
subprocess.call(f'git push -u origin {nm_brnch}')

But it's ugly, since it uses subprocess, instead of using GitPython.

I tried using GitPython, but without success:

repo.head.set_reference(nm_brnch)
repo.git.push("origin", nm_brnch)

I have consulted the following references:

4 Answers 4

3

I'm using gitpython==2.1.11 with Python 3.7. Below is my push function in which I first try a high-level push, and then a low-level push as necessary. Note how I check the return value of either command. I also log the push actions, and this explains what's happening at every step.

class GitCommandError(Exception):
    pass

class Git:
    def _commit_and_push_repo(self) -> None:
        repo = self._repo
        remote = repo.remote()
        remote_name = remote.name
        branch_name = repo.active_branch.name

        # Note: repo.index.entries was observed to also include unpushed files in addition to uncommitted files.
        log.debug('Committing repository index in active branch "%s".', branch_name)
        self._repo.index.commit('')
        log.info('Committed repository index in active branch "%s".', branch_name)

        def _is_pushed(push_info: git.remote.PushInfo) -> bool:
            valid_flags = {push_info.FAST_FORWARD, push_info.NEW_HEAD}  # UP_TO_DATE flag is intentionally skipped.
            return push_info.flags in valid_flags  # This check can require the use of & instead.

        push_desc = f'active branch "{branch_name}" to repository remote "{remote_name}"'
        log.debug('Pushing %s.', push_desc)
        try:
            push_info = remote.push()[0]
        except git.exc.GitCommandError:  # Could be due to no upstream branch.
            log.warning('Failed to push %s. This could be due to no matching upstream branch.', push_desc)
            log.info('Reattempting to push %s using a lower-level command which also sets upstream branch.', push_desc)
            push_output = repo.git.push('--set-upstream', remote_name, branch_name)
            log.info('Push output was: %s', push_output)
            expected_msg = f"Branch '{branch_name}' set up to track remote branch '{branch_name}' from '{remote_name}'."
            if push_output != expected_msg:
                raise RepoPushError(f'Failed to push {push_desc}.')
        else:
            is_pushed = _is_pushed(push_info)
            logger = log.debug if is_pushed else log.warning
            logger('Push flags were %s and message was "%s".', push_info.flags, push_info.summary.strip())
            if not is_pushed:
                log.warning('Failed first attempt at pushing %s. A pull will be performed.', push_desc)
                self._pull_repo()
                log.info('Reattempting to push %s.', push_desc)
                push_info = remote.push()[0]
                is_pushed = _is_pushed(push_info)
                logger = log.debug if is_pushed else log.error
                logger('Push flags were %s and message was "%s".', push_info.flags, push_info.summary.strip())
                if not is_pushed:
                    raise RepoPushError(f'Failed to push {push_desc} despite a pull.')
        log.info('Pushed %s.', push_desc)
Sign up to request clarification or add additional context in comments.

3 Comments

The key line of this code repo.git.push('--set-upstream', remote_name, branch_name) doesn't really use the gitpython API. It's just directly calling git. Is there no way to use the API to get a better formatted return value?
Is there a reason to not set the upstream in the first place? If setting it twice doesn't change the affect, might as well do that and eliminate the try-except
@Seanny123 The GitPython API seems to be slightly confusing on this point. AFAICT, the trick is to use the refspec argument in remote.push(): stackoverflow.com/a/66820830/9219425 . This answer has a bit more detail and links to a GitHub issue where this is discussed with the package author: stackoverflow.com/a/61034176/9219425
1

You have to define a remote repo, then push to it. e.g.

origin = repo.remote(name='origin')
origin.push()

See the Handling Remotes documentation for more examples of push/pull

Comments

0

Expanding on @Fraser's answer, here is the full code I used to successfully create a new branch:

from pathlib import Path

# initialize repo and remote origin
repo_path = Path("~/git/sandboxes/git-sandbox").expanduser()
repo = git.Repo(repo_path)
origin = repo.remote(name="origin")

# create new head and get it tracked in the origin
repo.head.reference = repo.create_head(branch_name)
repo.head.reference.set_tracking_branch(origin.refs.master).checkout()

# create a file for the purposes of this example
touch[f"{repo_path}/tmp1.txt"] & plumbum.FG

# stage the changed file and commit it
repo.index.add("tmp1.txt")
repo.index.commit("mod tmp1.txt")

# push the staged commits
push_res = origin.push(branch_name)[0]
print(push_res.summary)

Comments

0

Assuming it's the push that this is failing on in GitPython (as it was for me), just using GitPython I was able to solve this problem like this:

import git
repo = git.Repo('<your repo path>')
repo.git.checkout('HEAD', b=<your branch name>)
# -u fixed it for me
repo.git.push('origin', '-u', branch_name)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.