how to pull, push with remote branch - python

I'm trying to automate a change process which currently creates source code that gets manually pushed to Git. I'm trying to wrap that code using GitPython:
from git import *
# create the local repo
repo = Repo.init("/tmp/test/repo", bare=True)
assert repo.bare == True
# check status of active branch
repo.is_dirty()
# clone the remote repo
remote_repo = repo.clone("http://user:pass#git/repo.git")
# compile source code into repository
# ...
# track untracked files
repo.untracked_files
# commit changes locally
repo.commit("commit changes")
# push changes to master
remote_repo.push()
When I try running this, I get
Traceback (most recent call last):
File "git_test2.py", line 33, in
repo.commit("commit changes")
BadObject: 636f6d6d6974206368616e676573
The script is able to pull the remote repository, but fails on commit. Is there a better approach to this?

Some of the functions you are using may not work the way you expect them to. Generally, Repo methods are not the equivalent of the git sub-command with the same name.
Repo.commit does not create a commit but retrieve an existing commit. Since there is not commit named “commit changes” in the repository, an exception is raised.
Repo.clone creates a clone of this repository in a directory called http: inside the repository's directory structure which is most probably not what you want.
If you are trying to clone a remote repository, this can be achieved in a single line:
repo = Repo.clone_from("http://user:pass#git/repo.git", "/tmp/test/repo")
See the API Reference for more information on how to use GitPython.

You can't commit against a bare repository. You can only push/pull to them. By parallel think about how you would do this locally. Try cloning a bare repo and doing the actions, they won't work.
I'm not intimately familiar with the pythonic git bindings, but would imagine that you would need to clone a working repository, optionally checkout a given branch instead of master, do your work, call git add against just that stuff, and then commit.
Also, repo.untracked_files is a no op that simply lists them, it doesn't add them.
Honestly it looks like you blindly copy pasted from https://pythonhosted.org/GitPython/0.3.1/tutorial.html without actually reading what it had to say.
you'll need to manipulate the Index Object for example
index = repo.index
for ( path, stage ), entry in index.entries.iteritems: pass
index.add(['SOMEFILE'])
new_commit = index.commit("YOUR COMMIT MESSAGE")
#do somethign with new commit
Another example I found
import git
repo = git.Repo( '/home/me/repodir' )
print repo.git.status()
# checkout and track a remote branch
print repo.git.checkout( 'origin/somebranch', b='somebranch' )
# add a file
print repo.git.add( 'somefile' )
# commit
print repo.git.commit( m='my commit message' )
# now we are one commit ahead
print repo.git.status()
# now push

Related

How to do a quick "pull-merge" in gitpython?

EDIT: this was edited because I made a mistake in the example.
Assume I have a very simple git structure:
A single remote branch, called "brem"
A local, called "bloc"
self._repo = git.Repo(args.full_path_to_repo)
Having made a change to a bloc, I can push it to the remote using
self._repo.git.add(update=True)
self._repo.index.commit("commit_message")
origin = self._repo.remote(name=self.origin_name)
origin.push()
I would like to now pull and merge this change into the another local repo.
I do
def _pull(self):
origin = self._repo.remote(name=self.origin_name)
try:
origin.pull()
except git.exc.GitCommandError as ex:
print "There was nothing new to pull"
return
which pulls the remote correctly.
Then I try
origin = self._repo.remote(name=self.origin_name)
self._repo.git.merge(origin)
which gives an exception:
stderr: 'merge: bitbucket_repo - not something we can merge'
What is the correct way of merging pulled changes from a remote branch brem to a local branch bloc, assuming no conflicts?
EDIT2:
I managed to get it working with simply
self._repo.git.execute('git pull')
But this defeats the purpose of using a library.
I would still like to see the correct way of doing this
You'll need to specify a git reference from the remote to merge into the current HEAD. If it's a branch and you know the branch name you would do:
self._repo.git.merge(origin.heads["branch_name"])
See docs on accessing references in gitpython here: https://gitpython.readthedocs.io/en/stable/tutorial.html#advanced-repo-usage

How to check out a branch with GitPython

I have cloned a repository with GitPython, now I would like to checkout a branch and update the local repository's working tree with the contents of that branch. Ideally, I'd also be able to check if the branch exists before doing this. This is what I have so far:
import git
repo_clone_url = "git#github.com:mygithubuser/myrepo.git"
local_repo = "mytestproject"
test_branch = "test-branch"
repo = git.Repo.clone_from(repo_clone_url, local_repo)
# Check out branch test_branch somehow
# write to file in working directory
repo.index.add(["test.txt"])
commit = repo.index.commit("Commit test")
I am not sure what to put in the place of the comments above. The documentation seems to give an example of how to detach the HEAD, but not how to checkout an named branch.
If the branch exists:
repo.git.checkout('branchename')
If not:
repo.git.checkout('-b', 'branchename')
Basically, with GitPython, if you know how to do it within command line, but not within the API, just use repo.git.action("your command without leading 'git' and 'action'"), example: git log --reverse => repo.git.log('--reverse')

GitPython: Pull/Checkout from remote, discard local changes

For deploying files to some target (Windows) computers, I wanted to create a Python module I can feed with the necessary parameters.
The module should then check if the specified repo exists in the outputpath.
a) If it doesn't exist: clone the latest commit from remote
b) If it exists: discard all local changes, pull the latest commit from the remote
A way (that at least this worked for me) would be to delete the local target folder, recreate it and clone everything again.
My code, that only works for an empty dir:
stderr: 'fatal: remote origin already exists.'
import git, os, shutil
#outputfolder there?
if not os.path.exists(MY_outputfolder):
os.makedirs(MY_outputfolder)
repowrk = git.Repo.init(MY_outputfolder)
wrkr = repowrk.create_remote('origin',MY_REMOTE_URL)
wrkr.fetch()
wrkr.pull(wrkr.refs[0].remote_head)
print("---- DONE ----")
If the repo exists and you want to discard all local changes, and pull latest commit from remote, you can use the following commands:
# discard any current changes
repo.git.reset('--hard')
# if you need to reset to a specific branch:
repo.git.reset('--hard','origin/master')
# pull in the changes from from the remote
repo.remotes.origin.pull()
With these commands you don't have to delete the repo and clone again.
You can check the doc here for more information.
This is the code, that solved my problem.
a.) Output directory contains a .git folder: Assume, this is a local repo. Revert all local changes, purge unversioned files
b.) Output directory does not contain a .git folder (or filetree does not exist): Assume the target directory is dirty or not a local repository. Delete target tree and clone the remote directory to the specified target.
outdir_checker = outdir+'\.git'
if os.path.exists(outdir_checker):
repo_worker = git.Repo.init(outdir)
repo_worker.git.fetch(remote_url)
repo_worker.git.reset('--hard')
repo_worker.git.clean('-fdx')
print('git dir not created; already existed')
if not os.path.exists(outdir_checker):
shutil.rmtree(outdir, ignore_errors=True)
os.makedirs(outdir)
git.Repo.clone_from(remote_url, outdir)
print('git dir created')
For those who would prefer to use gitpython proper, rather than the codified version of the command line interface:
# Create a new branch
new_branch = repo.create_head("new_branch")
# Point your head to the new branch. Note that no working tree changes have taken place yet
repo.head.reference=new_branch
# Perform a reset. Note that index and working_tree must be set to True
# to ensure that the staging area and working tree are overwritten
repo.head.reset(index=True, working_tree=True)

Replicating "git checkout <commit> with PyGit2

I am trying to replicate the behaviour of the command "git checkout (commit)" where (commit) is the reference to as specific commit and not a branch name.
When using this command, the 'HEAD' of the repository point to the commit (detached head) and the working directory is in the same state that it was in this commit.
For the moment, I managed to make the HEAD of the repository point to a commit with PyGit2 :
def go(self, repo_name, version):
repo = pygit2.Repository(bdd[repo_name])
#commit = repo.revparse_single(version)
#repo.reset(version, pygit2.GIT_RESET_HARD)
repo.set_head(pygit2.Oid(hex=version))
print repo.head_is_detached
My problem is that I can't find how to rollback the working directory like the Git CLI does.
I tried using:
repo.checkout_head() : It doesn't do anything on the working directory.
repo.checkout() : Crash with a GitError: X conflicts prevent checkout
Is there a way to replicate this behaviour without using Repository.reset(ref, pygit2.GIT_RESET_HARD) ?
Low-level checkout is git read-tree -um HEAD $target && git update-ref HEAD $target; pygit2 apparently understands only the one-tree read and none of the options on that, so however it's doing checkout and merge and any number of other operations it doesn't offer more than a crude mock-up of actual git. It looks like you could kludge it with add a ref to your commit, check that out, then reset HEAD and delete the ref.
repo.checkout_tree can directly use a commit. After it's done, you still need the set_head. Also, pass the following strategy to checkout_tree if you want 'git reset' equivalent: pygit2.GIT_CHECKOUT_FORCE | pygit2.GIT_CHECKOUT_RECREATE_MISSING

Cloning only the main branch using PyGit2

I want to clone some remote repositories, but only retrieving the main branch.
My code currently gets all of the branches.
def init_remote(repo, name, url):
# Create the remote with a mirroring url
remote = repo.remotes.create(name, url, "+refs/*:refs/*")
# And set the configuration option to true for the push command
mirror_var = "remote.{}.mirror".format(name)
repo.config[mirror_var] = True
# Return the remote, which pygit2 will use to perform the clone
return remote
pygit2.clone_repository(url, "../../clones/"+location, remote=init_remote)
Your code doesn't just get all the branches, it mirrors the remote, getting its remote-tracking branches as well, which can lead to some confusing layout.
You're already setting your own refspec, so what you need to do is set the refspec to download the default branch. If you know it you can change the code to get just the one branch
remote = repo.remotes.create(name, url, "+refs/heads/master:refs/heads/master")

Categories

Resources