GitPython nothing appears in working copy after pull - python

I'm new in PythonGit and I have problem with pulling and pushing. I created locally bare repo and pushed to it an initial commit. After that I tried to init new user repo using PythonGit, fetch it and pull from it. I have no problem with initialize the repo however I can't get anything from remote/bare repo. My code:
import git
repo = git.Repo.init('.')
origin = repo.create_remote('origin', '/home/paweber/git/my-repo.git')
origin.fetch()
repo.create_head('master', origin.refs.master).set_tracking_branch(origin.refs.master)
origin.pull()
In ipython console for fetch and pull I get:
In [5]: origin.fetch()
Out[5]: [<git.remote.FetchInfo at 0x7f4a4d6ee630>]
for fetch and
In [6]: origin.pull()
Out[6]: [<git.remote.FetchInfo at 0x7f4a4d6e6ee8>]
for pull. After pull action, nothing is pulled at all and repo is still empty but exists. What I'm doing wrong?

pull() doesn't do anything as master is already at its destination commit, the one pointed to by origin/master.
This code will work as expected:
import git
repo = git.Repo.init('.')
origin = repo.create_remote('origin', '/home/paweber/git/my-repo.git')
origin.fetch()
# the HEAD ref usually points to master, which is 'yet to be born'
repo.head.ref.set_tracking_branch(origin.refs.master)
origin.pull()

I don't know how properly resolve this problem but the only idea is as phobic say to reset hard repo after create_head.
import git
repo = git.Repo.init('.')
origin = repo.create_remote('origin', '/home/paweber/git/my-repo.git')
origin.fetch()
repo.create_head('master', origin.refs.master).set_tracking_branch(origin.refs.master)
origin.pull()
repo.head.reset('--hard')
After that all further pulls should work properly.

Related

Get commits from a specific branch in github

Want to get the commits from a different branch rather than the master branch. It lists the commits from the master branch.I have 2 branch in my Repo master and test, i want the commit from the test branch instead of master.
I have already tried the below to get the list from the github repo but it gives the commits for master branch
github_commits = repo.get_commits()
full code that i have tried:
from github import Github
g = Github(base_url="https://my_hostnaame/api/v3",
login_or_token="my_access_token")
org = g.get_organization("my_org")
repo = org.get_repo("my_repo_name")
github_commits = repo.get_commits()
print(github_commits)
you need to do the following:
branch = g.get_repo("my_repo_name").get_branch("master")
print(branch.commit)
I assumed you installed PyGithub
here is the full usage of branch method

How to reset head of master branch to a previous commit with GitPython

I want to essentially revert changes in my master branch.
I am able to find my history of commits by doing:
import git
repo = git.Repo('repos/my-repo')
commits = repo.iter_commits('master',max_count=10)
but I am unsure as to how to point the head to, lets say, a commit where the message contains "reset to me". I am aware of repo.git.reset('--hard'), but I don't know how to properly use it. Thank you
If you know the commit number as in Latest Commit = 1, Second = 2, etc. then you can use ~ operator along with HEAD to point to the commit. HEAD~1 = Latest commit, HEAD~2 = second latest commit.
Hence to remove the latest commit, you can use:
import git
repo = git.Repo('repos/my-repo')
repo.head.reset('--hard HEAD~1', index=True, working_tree=True)
Refer this question to learn more about how to identify a commit.

Pygit2: Why does merge leave branch in an unclean state?

I'm currently running Pygit 0.24.1 (along with libgit 0.24.1), working on a repository where I have two branches (say prod and dev).
Every change is first commited to the dev branch and pushed to the remote repository. To do that, I have this piece of code:
repo = Repository('/foo/bar')
repo.checkout('refs/heads/dev')
index = repo.index
index.add('any_file')
index.write()
tree = index.write_tree()
author = Signature('foo', 'foo#bar')
committer = Signature('foo', 'foo#bar')
repo.create_commit('refs/heads/dev', author, committer, 'Just another commit', tree, [repo.head.get_object().hex])
up = UserPass('foo', '***')
rc = RemoteCallbacks(credentials=up)
repo.remotes['origin'].push(['refs/heads/dev'], rc)
This works quite fine, I can see the local commit and also the remote commit, and the local repo remains clean:
nothing to commit, working directory clean
Next, I check-out to the prod branch and I want to merge the HEAD commit on dev. To do so, I use this other piece of code (assuming I always start checked-out to the dev branch):
head_commit = repo.head
repo.checkout('refs/heads/prod')
prod_branch_tip = repo.lookup_reference('HEAD').resolve()
prod_branch_tip.set_target(head_commit.target)
rc = RemoteCallbacks(credentials=up)
repo.remotes['origin'].push(['refs/heads/prod'], rc)
repo.checkout('refs/heads/dev')
I actually can see the branch being merged both locally and remotely, but after this piece of code runs, the commited file always remains in a modified state on branch dev.
On branch dev
Changes to be committed:
(use "git reset HEAD ..." to unstage)
modified: any_file
I'm completely sure noone is modifying that file, though. Actually, a git diff shows nothing. This issue happens only with already commited files (i.e., files that have been commited at least once previously). When files are new, this works perfectly and leaves the file in a clean state.
I'm sure I'm missing some detail but I'm unable to find out what is it. Why is the file left as modified?
EDIT: Just to clarify, my aim is to do a FF (Fast-Forward) merge. I know there's some documentation about doing a non-FF merge in the Pygit2 documentation, but I'd prefer the first method because it keeps commit hashes through branches.
EDIT 2: After #Leon's comment, I double checked and indeed, git diff shows no output while git diff --cached shows the content that the file had before commiting. That's odd since I can see the change successfully commited on the local and remote repositories, but it looks like afterwards the file is changed again to the previous content...
An example of that:
Having a file with content '12345' commited + pushed, I replace that string with '54321'
I run the code above
git log shows the file commited correctly, on the remote repo I see the file with content '54321', while locally git diff --cached shows this:
## -1 +1 ##
-54321
+12345
I would explain the observed problem as follows:
head_commit = repo.head
# This resets the index and the working tree to the old state
# and records that we are in a state corresponding to the commit
# pointed to by refs/heads/prod
repo.checkout('refs/heads/prod')
prod_branch_tip = repo.lookup_reference('HEAD').resolve()
# This changes where refs/heads/prod points. The index and
# the working tree are not updated, but (probably due to a bug in pygit2)
# they are not marked as gone-out-of-sync with refs/heads/prod
prod_branch_tip.set_target(head_commit.target)
rc = RemoteCallbacks(credentials=up)
repo.remotes['origin'].push(['refs/heads/prod'], rc)
# Now we must switch to a state corresponding to refs/heads/dev. It turns
# out that refs/heads/dev points to the same commit as refs/heads/prod.
# But we are already in the (clean) state corresponding to refs/heads/prod!
# Therefore there is no need to update the index and/or the working tree.
# So this simply changes HEAD to refs/heads/prod
repo.checkout('refs/heads/dev')
The solution is to fast-forward the branch without checking it out. The following code is devoid of the described problem:
head_commit = repo.head
prod_branch_tip = repo.lookup_branch('prod')
prod_branch_tip.set_target(head_commit.target)
rc = RemoteCallbacks(credentials=up)
repo.remotes['origin'].push(['refs/heads/prod'], rc)

GitPython: get list of remote commits not yet applied

I am writing a Python script to get a list of commits that are about to be applied by a git pull operation. The excellent GitPython library is a great base to start, but the subtle inner workings of git are killing me. Now, here is what I have at the moment (simplified and annotated version):
repo = git.Repo(path) # get the local repo
local_commit = repo.commit() # latest local commit
remote = git.remote.Remote(repo, 'origin') # remote repo
info = remote.fetch()[0] # fetch changes
remote_commit = info.commit # latest remote commit
if local_commit.hexsha == remote_commit.hexsha: # local is updated; end
return
# for every remote commit
while remote_commit.hexsha != local_commit.hexsha:
authors.append(remote_commit.author.email) # note the author
remote_commit = remote_commit.parents[0] # navigate up to the parent
Essentially it gets the authors for all commits that will be applied in the next git pull. This is working well, but it has the following problems:
When the local commit is ahead of the remote, my code just prints all commits to the first.
A remote commit can have more than one parent, and the local commit can be the second parent. This means that my code will never find the local commit in the remote repository.
I can deal with remote repositories being behind the local one: just look in the other direction (local to remote) at the same time, the code gets messy but it works. But this last problem is killing me: now I need to navegate a (potentially unlimited) tree to find a match for the local commit. This is not just theoretical: my latest change was a repo merge which presents this very problem, so my script is not working.
Getting an ordered list of commits in the remote repository, such as repo.iter_commits() does for a local Repo, would be a great help. But I haven't found in the documentation how to do that. Can I just get a Repo object for the Remote repository?
Is there another approach which might get me there, and I am using a hammer to nail screws?
I know this is ages old but I just had to do this for a project and…
head = repo.head.ref
tracking = head.tracking_branch()
return tracking.commit.iter_items(repo, f'{head.path}..{tracking.path}')
(conversely to know how many local commits you have pending to push, just invert it: head.commit.iter_items(repo, f'{tracking.path}..{head.path}'))
I realized that the tree of commits was always like this: one commit has two parents, and both parents have the same parent. This means that the first commit has two parents but only one grandparent.
So it was not too hard to write a custom iterator to go over commits, including diverging trees. It looks like this:
def repo_changes(commit):
"Iterator over repository changes starting with the given commit."
number = 0
next_parent = None
yield commit # return the first commit itself
while len(commit.parents) > 0: # iterate
same_parent(commit.parents) # check only one grandparent
for parent in commit.parents: # go over all parents
yield parent # return each parent
next_parent = parent # for the next iteration
commit = next_parent # start again
The function same_parent() alerts when there are two parents and more than one grandparent. Now it is a simple matter to iterate over the unmerged commits:
for commit in repo_changes(remote_commit):
if commit.hexsha == local_commit.hexsha:
return
authors.append(remote_commit.author.email)
I have left a few details out for clarity. I never return more than a preestablished number of commits (20 in my case), to avoid going to the end of the repo. I also check beforehand that the local repo is not ahead of the remote repo. Other than that, it is working great! Now I can alert all commit authors that their changes are being merged.

How to add something to the index, commit it, then push the master branch to a named remote with dulwich?

How can I add something to the index, as in
git add .
then
git commit -m "message"
then
git push origin master
using dulwich?
So far I've found this http://www.samba.org/~jelmer/dulwich/apidocs/dulwich.index.Index.html but it doesn't say much, does it?
Thanks
This is not a tested answer but it is closer on the push part:
# set wants to master
def wantmaster(haves, wants):
global repo
return { "refs/heads/master": repo.refs["HEAD"] }
client, src = dulwich.client.get_transport_and_path(origin_uri)
client.send_pack(src, wantmaster, repo.object_store.generate_pack_contents)
A variation on this is working in my code.
In this case, you don't want the index but the repo (of which the index is a part). http://www.samba.org/~jelmer/dulwich/apidocs/dulwich.repo.Repo.html
Something like this should work:
>>> from dulwich.repo import Repo
>>> x = Repo('.')
>>> x.stage(['a'])
>>> x.do_commit(message="foo")
'151915d47467696d2f9d18de6f61be7168682aeb'

Categories

Resources