Check latest commit with Python in Linux/Windows/Mac - python

I'm trying to generate a simple Python code that:
checks if it is running inside a git folder
if so, fetch the latest commit, else skip
it should work under the three platforms: Linux, Windows, and Mac
I have this code that works correctly under Linux:
from subprocess import call, STDOUT
import os
if call(["git", "branch"], stderr=STDOUT, stdout=open(os.devnull, 'w')) != 0:
# Not a git folder
commit = ''
else:
# Inside a git folder.: fetch latest commit
commit = subprocess.check_output(['git', 'rev-parse', '{}'.format('HEAD')])
print(commit)
but I have no way of checking if itwill work under Windows and Mac.
Does it work? Is there any way of checking/knowing this sort of things when one has no access to the other operating system?

You don't want to run git branch to detect whether you're in a Git repository, because you may or may not have any branches. To detect whether you're able to use Git commands, you'll want to run something like git rev-parse --git-dir, which will exit non-zero if you're not within a Git repository.
However, there are a couple of other issues with your code. First of all, in a new repository (one created fresh with git init), there will be a .git directory and the above command will succeed, but HEAD will not point anywhere. Therefore, your git rev-parse HEAD command will fail and print HEAD and an error.
Finally, if you want parse a revision, you should usually use --verify so that you don't print the dummy HEAD value on failure. So your invocation should look like git rev-parse --verify HEAD.
Ultimately, it's up to you to figure out what you want to do in a newly initialized repository, whether that's fail or fall back to an empty string.
The behaviors I've described here are consistent across platforms; they're built into Git and well defined.

There's a method check_output in subprocess library
from subprocess import check_output
try:
# use python to parse this log for info. This is your entire last commit
logs = check_output(['git', 'log', '-1', '--stat']).decode("UTF-8")
except Exception as e:
# Do whatever you wanna do otherwise if not git repository
print(e)
Git has a command called "git log".
"-1" indicates the last commit and
--stat will give you the files that were changed, commit ID, TIME ETC
then you can use python to parse this log and retrive any information you want
Check this out for more info on git log

Related

python script: git checkout prior to running

I've started to use gitHub to manage the development process of production scripts running daily on my workstation (via cron).
One way to make sure that the lastest valid production version runs would be to run a git checkout inside the production directory, moments prior to running the target script. I was wondering if it can be done from within the production script (i.e. checking is this is the latest version, if not, git checkout, if yes, do nothing and run)
It is certainly possible to do this, e.g. (untested):
git fetch &&
ahead=$(git rev-list --count master..origin/master) &&
case "$ahead" in
0) ;; # run normally
*) echo "I seem to be out of date"
git merge --ff-only || { echo "update failed, quitting"; exit 1; }
exec <path-to-script>;;
esac
# ... normal part of script here
But this is also almost certainly the wrong approach. Instead of doing that, schedule a job—a script—that consists of:
git fetch && git merge --ff-only && exec <path-to-script>
This script can live in the same repository. It's a separate script whose job is to update in place—which, if there's nothing to do, is a no-op (it says "Already up to date." and then exits 0 = success)—and then run the other script, whether it's updated or not. This provides a clean separation of purpose: one script updates; one script runs; there's no weird mix of self-update-and-oops-now-I-have-to-quit-because-maybe-my-code-is-different.
Note that adding --quiet to the git merge --ff-only suppresses the "Already up to date." message, which may be helpful if your version of cron emails you the output when there is output. (If your version of cron doesn't do this, it probably should be upgraded to one that does.) So you probably really want:
git fetch && git merge --ff-only --quiet && exec <path-to-script>
The fetch followed by merge is what git pull does by default, but git pull is a program meant to be run by a human. Git divides its various programs into so-called porcelain and plumbing, and the porcelain commands are the ones meant for humans, while the plumbing ones are meant for writing scripts. Git's division here is quite imperfect: some commands are both plumbing and porcelain, and there are some varieties that are missing (e.g., git log is porcelain, but there are no plumbing commands for some of what it does)—but to the extent that you can, it's usually wise to stick to this pattern.
In case it might be useful, here's the python script I am using now. I call it from vim immediately after a commit.
#!/bin/python3
"""
This script syncs development -> production branches
Both directories are connected to the same repository
Assume projectP (for production) and projectD (for development)
"""
####################################################
# Modules
####################################################
import git
####################################################
# Globals
####################################################
theProjectD = "path_to_projectD"
theProjectP = "path_to_projectP"
####################################################
# Code
####################################################
# push & merge develop to main from the develop directory
repo = git.Repo(theProjectD)
repo.remotes.origin.push()
repo.git.checkout('main')
repo.git.merge('develop')
repo.remotes.origin.push()
repo.git.checkout('develop')
# fetch latest version of main in the production directory
repo = git.Repo(theProjectP)
repo.remotes.origin.fetch()
repo.remotes.origin.pull()

How do I revert a commit with gitpython?

For a python script I'm writing (using Python 2.7 on Windows 7) I should be able to modify a branch with a given commit, that is adding it (cherry pick) if the commit is missing, or reverting it if it's already present.
Apparently revert has not been wrapped in gitpython's Repo class, so I tried to use Git directly with:
repo.git.revert(reference)
where reference is one of the commits returned by repo.iter_commits("master")
What happens is that the script locks on that command and becomes idle; I then have to kill the command prompt window.
If I go in the working directory and explore the repository, I can see (with git diff) that after the execution, the changes have been applied even tho' no new commit is visibile if I git log.
Any ideas about if and what I'm doing wrong?
I solved the mistery by trying to git commit the applied changes manually. Git complained about a swap file in the working directory.
So, the problem was that the command was being executed as if it was run from a terminal, hence waiting for me to somehow edit the commit message! So I needed to run the revert command with the no-edit option.
I changed the method invocation to:
repo.git.revert(reference.hexsha, no_edit = True)
(notice that gitpython requires the underscore as a separator. Also, using explicitly the hexsha property is not required, since reference would be converted to its str() representation anyway.)
It seems to work.

Rebasing Git Repo with Python

Is there a way to use use python to rebase a repo from one on github, then push the result. As well as detecting if the rebase failed as a result of conflicts that need to be resolved?
Git is primarily a command-line tool. Once installed, you should be able to open-up a console, command prompt, powershell, c-shell, bash shell, etc. and just type git and get a list of available git commands.
Once you have Git setup and working this way, then from Python it would be possible to execute git commands in the same way you would execute any other shell commands. I'm not a Python expert, but ElpieKay suggests in the comments to use:
commands.getstatusoutput("git <command>")
You will need to do a separate search for git rebase specifically and figure out how the output is formatted and parse it to determine success, or possibly there is an error code or StdErr output that you can get through the .getstatusoutput or a similar command from commands in python.
Another thing that may help is looking at the man page for rebase with git rebase --help.
Summary
I recommend doing a search to find out more about the python commands library or just shell interaction in general for python, and then a separate set of searches/research to determine exactly how to implement the git rebase commands and its output format to determine what you need to parse to determine success or failure.

Only call 'git stash pop' if there is anything to pop

I am creating a post-commit script in Python and calling git commands using subprocess.
In my script I want to stash all changes before I run some commands and then pop them back. The problem is that if there was nothing to stash, stash pop returns a none-zero error code resulting in an exception in subprocess.check_output(). I know how I can ignore the error return code, but I don't want to do it this way.
So I have been thinking. Is there any way to get the number of items currently in stash? I know there is a command 'git stash list', but is there something more suited for my needs or some easy and safe way to parse the output of git stash list?
Also appreciate other approaches to solve this problem.
Don't do that!
Suppose that git stash save saves nothing, but there are already some items in the stash. Then, when you're all done, you pop the most recent stash, which is not one you created.
What did you just do to the user?
One way to do this in shell script code is to check the result of git rev-parse refs/stash before and after git stash save. If it changes (from failure to something, or something to something-else), you have created a new stash, which you can then pop when you are done.
More recent versions of Git have git stash create, which creates the commit-pair as usual but does not put them into the refs/stash reference. If there is nothing to save, git stash create does nothing and outputs nothing. This is a better way to deal with the problem, but is Git-version-dependent.
You can simply try calling git stash show stash#{0}. If this returns successfully, there is something stashed.

Calling Git Binary From Python And Error Codes

I am trying to build a set of git utilities with python. I am using subprocess.Popen to run the git binary. Right now I am just trying to find the best way to determine that there was an error in running a git command.
My question is whether or not git will always return a returncode of 0 on a successful git command and always return a returncode of non-zero on a unsuccessful call? I just want to make sure that checking the returncode is a safe way to detect an error so that I can exit the script if a git command was unsuccessful.
Yes, git (and any well-behaved *nix program) will always return 0 for success and non-zero for failure. This is the paradigm on GNU/Linux systems, and since Git was made by the same person who made Linux, you can bet it follows the convention.

Categories