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:
Pushing local branch to remote branch
Use GitPython to Checkout a new branch and push to remote
Related GitHub issue/question
Tutorial from official docs
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)
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
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)
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)
Related
I am trying to write a github automation script but there is an issue that i am facing i.e. i don't know where the repository information i being stored in .git folder. By searching the files manually i found that .git\config file has some information but it is really inconsistent as sometimes the information is not there. Can someone tell me which file should i look into for repository information(i.e. URL , working branch).
This is my code to get the URL and branch from the .git\config file:-
import os
mypath = os.getcwd()
infofile = mypath + '/.git/config'
def takeInfo():
print('No Existing repo info found\n')
url = str(input('Enter the Github Repo URL: '))
branch = str(input('Enter the branch: '))
info = ['n' , url , branch]
return info
def checkinfoInDir():
if (os.path.exists(infofile)):
print('Repo Info Found:-')
with open(infofile, "r") as f:
info = f.readlines()
# print(info)
for ele in info:
if('url' in ele):
url = info[info.index(ele)].split()[2]
if('branch' in ele):
branch = info[info.index(ele)].split()[1].split('"')[1]
info = [url , branch]
else:
info = takeInfo()
return info
To read the current branch, use git symbolic-ref or git rev-parse:
git symbolic-ref works in all cases where HEAD is a symbolic ref, including the case where it is a symbolic name for a branch that does not yet exist.1 It fails when HEAD is detached, i.e., points directly to a commit.
git rev-parse --symbolic-full-name HEAD works in all cases where HEAD identifies some commit. It fails when HEAD is a symbolic name for a branch that does not yet exist, and when HEAD is detached, it comes up with the name HEAD.
To find a URL for some remote, use git config --get remote.remote.url. Note that if Git is configured for a triangular workflow, this is the fetch URL, not the push URL, so check for the result of git config --get remote.remote.pushurl to see if there is a separate push URL.
Note that there can be any number of remotes, including none.
1Git calls this an orphan branch or an unborn branch, depending on which part of Git is doing the calling. To get into this state, create a new empty repository—you'll be on whatever branch name you choose as your initial name, but it won't exist as there are no commits and no branch names can exist until there are commits—or use git checkout --orphan or git switch --orphan.
To get into detached HEAD state, use git checkout with any valid commit identifier that is not a branch name, or use git checkout --detach or git switch --detach with or without any valid commit identifier.
To get out of detached HEAD state, use git checkout or git switch with a valid branch name (including the various cases where these commands create a new branch name, then attach HEAD to it).
This is an example config file
[core]
repositoryformatversion = 0
filemode = false
bare = false
logallrefupdates = true
symlinks = false
ignorecase = true
[submodule]
active = .
[remote "origin"]
url = https://gitlab.com/sample/sample.git
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
remote = origin
merge = refs/heads/master
it has ini file format, so you use a simple ini python library to parse this file.
My organization is migrating from GitLab to GitHub and we were using some existing Python scripts to check commit difference and to create multiple release branches in one go by cloning the previous release branch. I know how we do this for GitLab but not able to find some solution to the same in GitHub. If someone can please help me with how can I do the same in GitHub too it will be really helpful.
We are using below code in gitlab for now
def createBranch(projectName, existingBranch, newBranchName):
projectId=projMap[projectName]
gl = gitlab.Gitlab('github URL', private_token='git token', ssl_verify=False)
project = gl.projects.get(projectId)
try:
project.branches.get(newBranchName)
log.info(" %s %s already exist", projectName, newBranchName)
return 0
except:
log.info("createBranch %s from:%s to:%s", project.name, existingBranch, newBranchName)
try:
project.branches.create({"branch": newBranchName,
"ref": existingBranch})
except:
raise Exception(project.name, " error creating " + newBranchName + " from " + existingBranch)
projMap -> this is one text file which stores all project name and their projectIDs.
I tried multiple threads in Stackoverflow but none one seems to help me.
PyGithub has the functionality you're looking for.
Connecting to the GitHub API
gh = Github(params)
Getting a repo:
repo = gh.get_repo(id)
Getting a branch:
branch = repo.get_branch(branch)
Making a branch:
repo.create_git_ref(ref='refs/heads/' + 'new_branch_name', sha=branch.commit.sha)
All a copy paste from the PyGithub reference page.
The code should do the following in order:
It should download/clone the public Github repository locally.
The should remove all the git history (and branches)
Use the Github API to create a new Github repository initialized with the input github repository content that you downloaded locally. The new repository should be named
using name supplied.
I am able to do 1 and 3 but asks for log-in 2 times. I am not able to initialize the new remote repo with local repo.
local_repo = repo1 how?
And removing git history? where can I find git history in the cloned repo.
import git,os,tempfile,os,fnmatch,sys
from github import Github
username = sys.argv[1]
password = sys.argv[2]
input_repo_url = sys.argv[3]
output_repo_name = sys.argv[4]
tempdir=tempfile.mkdtemp(prefix="",suffix="")
predictable_filename = "myfile"
saved_umask = os.umask(77)
path = os.path.join(tempdir,predictable_filename)
print("Cloning the repository at "+path)
local_repo = git.Repo.clone_from(input_repo_url,path, branch="master")
print("Clone successful!")
g = Github(username,password)
user = g.get_user()
repo1 = user.create_repo(output_repo_name)
print("New repository created at "+username+" account ")
print(repo1)
target_url = "https://github.com/"+username+"/"+output_repo_name+".git"
print(target_url)
print("Pushing cloned repo to target repo")
local_repo.create_remote("new",url=target_url)
local_repo.git.push("new")
print("Success!!")
I have created a text file using file operations in python. I want the file to be pushed to my existed GITLAB repository.
I have tried the below code where i get the created file in my local folders.
file_path = 'E:\My material\output.txt'
k= 'Fail/Pass'
with open (file_path, 'w+') as text:
text.write('Test case :' +k)
text.close()
What is the process or steps or any modifications in file_path to move the created text file to the GITLAB repository through python code.
Using python gitlab module :
We can push the file to the gitlab,but you need to follow the below steps:
Step 1) Clone the repository to your local
Step 2) Add the file to the clonned repository
Step 3) Push the code to the gitlab
code:
import gitlab
import base64
from gitlab import Gitlab
import shutil
callbacks = pygit2.RemoteCallbacks(pygit2.UserPass("Your_private_token", 'x-oauth-basic'))
repoClone = pygit2.clone_repository("https://gitlab.com/group/"+api_name+".git", local_clone_path,checkout_branch=target_branch,callbacks=callbacks) # clonning the repo to local
shutil.copy(src,dst_clone_repo_path) #copy your file to the cloned repo
repoClone.remotes.set_url("origin", "https://gitlab.com/group/"+api_name+".git")
index = repoClone.index
index.add_all()
index.write()
tree = index.write_tree()
oid = repoClone.create_commit('refs/heads/'+target_branch, author, commiter, "init commit",tree,[repoClone.head.peel().hex])
remote = repoClone.remotes["origin"]
credentials = pygit2.UserPass("your_private_token", 'x-oauth-basic') # passing credentials
remote.credentials = credentials
remote.push(['refs/heads/'+target_branch],callbacks=callbacks) # push the code to the gitlab repo
Do you mean executing Shell Commands with Python? Suppose this newly created file and this python script are both under the specific local git repository which connected with the remote repository you want to commit. our plan is packing all the bash command in os.system.
import os
os.system('git add E:\My material\output.txt; git commit -m "anything"; git push -u origin master')
update
import os
os.system('cd /local/repo; mv E:\My material\output.txt .; git add output.txt; git commit -m "anything"; git push -u origin master')
A bit late to the party but:
Using the gitlab python library you can do this:
def commit_file(project_id: int, file_path: str, gitlab_url: str, private_token: str, branch: str = "main") -> bool:
"""Commit a file to the repository
Parameters
----------
project_id: int
the project id to commit into. E.g. 1582
file_path: str
the file path to commit. NOTE: expecting a path relative to the
repo path. This will also be used as the path to commit into.
If you want to use absolute local path you will also have to
pass a parameter for the file relative repo path
gitlab_url: str
The gitlab url. E.g. https://gitlab.example.com
private_token: str
Private access token. See doc for more details
branch: str
The branch you are working in. See note below for more about this
"""
gl = gitlab.Gitlab(gitlab_url, private_token=private_token)
try:
# get the project by the project id
project = gl.projects.get(project_id)
# read the file contents
with open(file_path, 'r') as fin:
content = fin.read()
file_data = {'file_path': file_path,
'branch': branch,
'content': content,
'author_email': "your#email.com", # don't think this is required
'commit_message': 'Created a file'}
resp = project.files.create(file_data)
# do something with resp
except gitlab.exceptions.GitlabGetError as get_error:
# project does not exists
print(f"could not find no project with id {project_id}: {get_error}")
return False
except gitlab.exceptions.GitlabCreateError as create_error:
# project does not exists
print(f"could not create file: {create_error}")
return False
return True
Example is based on gitlab project files documentation python-gitlab package docs
Note that if you want to create the file in a new branch, you will have to also provide a start_branch attribute:
file_data = {'file_path': file_path,
'branch': your_new_branch,
'start_branch': base_branch,
'content': content,
'author_email': "your#email.com",
'commit_message': 'Created a file in new branch'}
Also, if you don't care for using the python-gitlab package, you can use the rest api directly (using request or something like that. The relevant documentation is here
I'm trying to write a bzr post-commit hook for my private bugtracker, but I'm stuck at the function signature of
post_commit(local, master, old_revno, old_revid, new_revno, mew_revid)
How can I extract the commit message for the branch from this with bzrlib in Python?
And the answer is like so:
def check_commit_msg(local, master, old_revno, old_revid, new_revno, new_revid):
branch = local or master
revision = branch.repository.get_revision(new_revid)
print revision.message
local and master are Branch objects, so once you have a revision, it's easy to extract the message.