How to use git log --oneline in gitpython - gitpython

I'm trying to extract list of commit messages by giving a start sha & end sha. It's easy in git using git log.
But am trying to do it through gitpython library.
Could someone help me to achieve this?
in Git the command is like this :
git log --oneline d3513dbb9f5..598d268f
how do i do it with gitpython?

The GitPython Repo.iter_commits() function (docs) has support for ref-parse-style commit ranges. So you can do:
import git
repo = git.Repo("/path/to/your/repo")
commits = repo.iter_commits("d3513dbb9f5..598d268f")
Everything after that depends on the exact formatting you want to get. If you want something similar to git log --oneline, that would do the trick (it is a simplified form, the tag/branch names are not shown):
for commit in commits:
print("%s %s" % (commit.hexsha, commit.message.splitlines()[0]))

You can use pure gitpython:
import git
repo = git.Repo("/home/user/.emacs.d") # my .emacs repo just for example
logs = repo.git.log("--oneline", "f5035ce..f63d26b")
will give you:
>>> logs
'f63d26b Fix urxvt name to match debian repo\n571f449 Add more key for helm-org-rifle\nbea2697 Drop bm package'
if you want nice output, use pretty print:
from pprint import pprint as pp
>>> pp(logs)
('f63d26b Fix urxvt name to match debian repo\n'
'571f449 Add more key for helm-org-rifle\n'
'bea2697 Drop bm package')
Take a note that logs is str if you want to make it a list, just
use logs.splitlines()
Gitpython had pretty much all similar API with git. E.g repo.git.log for git log and repo.git.show for git show. Learn more in Gitpython API Reference

You may want to try PyDriller (a wrapper around GitPython), it's easier:
for commit in Repository("path_to_repo", from_commit="STARTING COMMIT", to_commit="ENDING_COMMIT").traverse_commits():
print(commit.msg)
If you want commits of a specific branch, add the parameter only_in_branch="BRANCH_NAME". Docs: http://pydriller.readthedocs.io/en/latest/

Related

Implement git branch --contains with rugged library

I'm working with a ruby script that execute the following git command on a given git repository.
branches = `git branch -a --contains #{tag_name}`
This approach has some drawbacks with command output (that may change in different git versions) and is subject to git binary version on the hosting machine, so I was trying to see if it's possible to replace that command using rugged but I wasn't able to find anything similar to that.
Maybe in rugged there's no way to implement --contains flag, but I think it should be pretty easy to implement this behavior:
Given any git commit-ish (a tag, a commit sha, etc.) how to get (with rugged) the list of branches (both local and remote) that contains that commit-ish?
I need to implement something like github commit show page, i.e. tag xyz is contained in master, develop, branch_xx
Finally solved with this code:
def branches_for_tag(tag_name, repo_path = Dir.pwd)
#branches ||= begin
repo = Rugged::Repository.new(repo_path)
# Convert tag to sha1 if matching tag found
full_sha = repo.tags[tag_name] ? repo.tags[tag_name].target_id : tag_name
logger.debug "Inspecting repo at #{repo.path}, branches are #{repo.branches.map(&:name)}"
# descendant_of? does not return true for it self, i.e. repo.descendant_of?(x, x) will return false for every commit
# #see https://github.com/libgit2/libgit2/pull/4362
repo.branches.select { |branch| repo.descendant_of?(branch.target_id, full_sha) || full_sha == branch.target_id }
end
end

bash completion random characters

I am having trouble with bash_completion. When I expand variables, I am fine, but when I use a commands completion (such as git or vim-addon-manager), then the completion throws random characters in there. This didn't use to happen to me, I can't figure out what it is.
This is an example of what happens when I type gitTabTaby
[11:11] me#my_computer:~ $ git
Display all 131 possibilities? (y or n)
^[[01;31m^[[K c^[[m^[[Kheckout delete-tag f^[[m^[[Kmt-merge-msg i^[[m^[[Knit-db notes rm
a^[[m^[[Kdd c^[[m^[[Kheckout-index d^[[m^[[Kaemon f^[[m^[[Kor-each-ref i^[[m^[[Knstaweb obliterate setup
a^[[m^[[Klias c^[[m^[[Kheck-ref-format d^[[m^[[Kelete-branch f^[[m^[[Kormat-patch info p4 shortlog
a^[[m^[[Km c^[[m^[[Kherry d^[[m^[[Kelete-merged-branches f^[[m^[[Ksck line-summary pull show
a^[[m^[[Knnotate c^[[m^[[Kherry-pick d^[[m^[[Kelete-submodule f^[[m^[[Ksck-objects l^[[m^[[Kog pull-request show-branch
a^[[m^[[Kpply c^[[m^[[Klean d^[[m^[[Kescribe fresh-branch l^[[m^[[Ks-files push show-tree
a^[[m^[[Krchive c^[[m^[[Klone d^[[m^[[Kiff g^[[m^[[Kc l^[[m^[[Ks-remote rebase squash
a^[[m^[[Krchive-file c^[[m^[[Kolumn d^[[m^[[Kiff-files g^[[m^[[Ket-tar-commit-id l^[[m^[[Ks-tree refactor stage
b^[[m^[[Kack c^[[m^[[Kommit d^[[m^[[Kiff-index g^[[m^[[Krep local-commits reflog stash
b^[[m^[[Kisect c^[[m^[[Kommits-since d^[[m^[[Kifftool graft mergetool release status
b^[[m^[[Klame c^[[m^[[Kommit-tree d^[[m^[[Kiff-tree h^[[m^[[Kash-object m^[[m^[[Kailinfo relink submodule
b^[[m^[[Kranch c^[[m^[[Konfig effort h^[[m^[[Kelp m^[[m^[[Kailsplit remote subtree
b^[[m^[[Kug c^[[m^[[Kontrib extras h^[[m^[[Kttp-backend m^[[m^[[Kerge rename-tag summary
b^[[m^[[Kundle c^[[m^[[Kount feature h^[[m^[[Kttp-fetch m^[[m^[[Kerge-base repack tag
c^[[m^[[Kat-file c^[[m^[[Kount-objects f^[[m^[[Kast-export h^[[m^[[Kttp-push m^[[m^[[Kerge-file repl touch
c^[[m^[[Khangelog c^[[m^[[Kreate-branch f^[[m^[[Kast-import ignore m^[[m^[[Kerge-index replace undo
c^[[m^[[Kheck-attr c^[[m^[[Kredential f^[[m^[[Ketch i^[[m^[[Kmap-send m^[[m^[[Kerge-octopus request-pull whatchanged
c^[[m^[[Kheck-ignore c^[[m^[[Kredential-cache f^[[m^[[Ketch-pack i^[[m^[[Kndex-pack mv reset
c^[[m^[[Kheck-mailmap c^[[m^[[Kredential-store f^[[m^[[Kilter-branch i^[[m^[[Knit name-rev revert
Another example is vam tetris (vam tetTabTab):
^[[01;31m^[[Kaddon: tet^[[m^[[Kris
For vam install tetTabTab, it actually renders it an invalid argument (it's also quite difficult to read), so how can I fix this?
I was experiencing the same problem and saw your answer and changed:
export GREP_OPTIONS='--color=always' to export GREP_OPTIONS='--color=auto'This seems to have fixed the problem with bash-completion on my Mac.
Apparently, bash completions don't like when grep is colored. Anything like
alias grep='grep --color=always'
alias fgrep='fgrep --color=always'
alias egrep='egrep --color=always'
will give you problems.
Therefore, as Garrett Bellomy details below, it may be wise to use --color=auto, which can be achieved by setting GREP_OPTIONS (or by aliasing grep in your rc file). If you want to make this a global variable, add this to ~/.bash_profile (for bash) or ~/.zprofile (zsh) depending on your default shell: export GREP_OPTIONS='--color=auto'

Enforce git branch policies

I'm trying to enforce a company policy, taking these assumptions:
There are only 3 available upstream branches: master, version/* and hotfix/*.
Master branch accepts only non-forwarded merge commits.
Version and Hotfix branches accept only fast-forward/rebased commits.
Master branch must only be merged into from Version or Hotfix branches.
Version and Hotfix branches must diverge from Master branch directly.
So far this is what I come up with:
#!/usr/bin/env ruby
# Encoding: utf-8
$oldrev, $newrev, $refname = STDIN.read.split(" ")
$real_refname = `git rev-parse --abbrev-ref #{$refname} 2> /dev/null`.strip
$merge_commits = `git rev-list --merges #{$oldrev}..#{$newrev} 2> /dev/null`.strip
$parent_commit = `git rev-parse #{$newrev}\^1`
$ancestor_branch = `git show-branch | grep '*' | grep -v '#{$real_refname}' | head -n1 | sed 's/.*\[\(.*\)\].*/\1/' | sed 's/[\^~].*//'`
puts "Enforcing Policies... \n(#{$real_refname}) (#{$oldrev[0,6]}) (#{$newrev[0,6]})"
$errors = []
def check_branch_policy()
$errors.push "Branch #{$real_refname}: Only Version, Hotfix and Master branches are allowed to be pushed upstream." if !$real_refname.match(/^(version\/[1-9.]+|hotfix\/[1-9.]+|master)/)
$errors.push "Branch #{$real_refname}: Master branch accepts only non-forwarded merge commits." if $real_refname.match('master') && (!$merge_commits.match($newrev) || !$parent_commit.match($oldrev))
$errors.push "Branch #{$real_refname}: Version and Hotfix branches accept only fast-forward/rebased commits." if !$real_refname.match('master') && !$merge_commits.empty?
$errors.push "Branch #{$real_refname}: Version and Hotfix branches must diverge from Master branch directly." if !$real_refname.match('master') && !$ancestor_branch[4,6].match('master')
false
end
check_branch_policy
unless $errors.empty?
puts '[POLICY] Invalid git branch rules.'
$errors.each { |error| puts "# #{error}" }
exit 1
end
A few issues though:
First, I'd be glad for a general code review. I'm not a rubyist whatsoever, and I just patched around things I found on the web. So the code is probably pretty bad.
Is there an easier way to enforce the "Master branch accepts only non-forwarded merge commits."?
sed and grep doesn't seem to play well with git hooks, so I basically need an alternative to the current $ancestor_branch command. Didn't come up with anything yet.
When first pushing a branch, $real_refname doesn't work - it can't seem to abbrev-ref properly.
I can't seem to find a way to enforce "Master branch must only be merged into from Version or Hotfix branches." yet. Any ideas?
EDIT #1 - 25.05.14
After tinkering around a little bit I got to this:
#!/usr/bin/env ruby
# Encoding: utf-8
oldrev, newrev, refname = STDIN.read.split(" ")
short_refname = refname[11..-1]
merge_commits = `git rev-list --merges #{oldrev}..#{newrev}`.strip
unique_revs = `git rev-list --all --not $(git rev-list --all ^#{newrev})`
missed_revs = `git rev-list #{oldrev}..#{newrev}`
puts "Enforcing Policies... \n(#{short_refname}) (#{oldrev[0,6]}) (#{newrev[0,6]})"
def check_branch_policy(oldrev,newrev,short_refname,merge_commits,unique_revs,missed_revs)
errors = []
errors << "Only Version, Hotfix and Master branches are allowed to be pushed upstream." if
!short_refname[/^(version\/[1-9.]+|hotfix\/[1-9.]+|master)/]
if short_refname['master']
# Master should have only one unique commit each time - the merge commit (newrev).
errors << "Master branch accepts only non-forwarded merge commits, one at a time." if
!merge_commits[newrev] && missed_revs.count > 2
else
# If not empty, it means there's a merge commit - whereas there shouldn't be.
errors << "Version and Hotfix branches accept only fast-forward/rebased commits." if
!merge_commits.empty?
# If not equal, it means at least one commit is reachable from another ref - meaning it was diverged.
errors << "Version and Hotfix branches must diverge from Master branch directly." if
!unique_revs[missed_revs]
end
errors
end
errors = check_branch_policy(oldrev,newrev,short_refname,unique_revs,missed_revs)
unless errors.empty?
puts '[POLICY] Invalid git branch rules.'
errors.each { |error| puts "# Branch #{short_refname}: #{error}" }
exit 1
end
More questions arose though:
Is there a way to serve the local variables without calling them in the method? Otherwise the script throws an error.
I managed to find a way to retrieve the short_refname, but it's not so elegant. I read somewhere I can use short_refname = refname.chomp("refs/heads/") but it doesn't seem to work. Help?
I found a way (clever? too complex? go figure) to find if a branch has diverged where it shouldn't have but this brings two issues - I can't get all the refs from the hook. --stdin flag doesn't seem to cut it. Further, the exclude flag (^some_ref) doesn't work inside the hook, whereas in the terminal it works fine. Ideas?
Assuming I move this script to update hook, how can I get the refnames? The web sources weren't so clear so far...
Lets first focus on the ruby part:
There is hardly ever a reason to use global variables in ruby. And in a script they are in a "global" scope anyway => get rid of the preceding $ in variable names
In this code:
$errors = []
def check_branch_policy()
$errors.push "Branch #{$real_refname}: Only Version, Hotfix and Master branches are allowed to be pushed upstream." if !$real_refname.match(/^(version\/[1-9.]+|hotfix\/[1-9.]+|master)/)
$errors.push "Branch #{$real_refname}: Master branch accepts only non-forwarded merge commits." if $real_refname.match('master') && (!$merge_commits.match($newrev) || !$parent_commit.match($oldrev))
$errors.push "Branch #{$real_refname}: Version and Hotfix branches accept only fast-forward/rebased commits." if !$real_refname.match('master') && !$merge_commits.empty?
$errors.push "Branch #{$real_refname}: Version and Hotfix branches must diverge from Master branch directly." if !$real_refname.match('master') && !$ancestor_branch[4,6].match('master')
false
end
check_branch_policy
It's bad style to write a method (or a function) which just works on a global object created only for this purpose. You might as well just remove the method definition, because it does nothing here. This is not particular "ruby style" thing but applies to programming in general. The better solution is to just create the object inside the method and return it. I also don't like these long unreadable lines. So in total would probably structure it more like this:
def check_branch_policy
errors = []
errors << "Only Version, Hotfix and Master branches are allowed to be pushed upstream." if
!real_refname[/^(version\/[1-9.]+|hotfix\/[1-9.]+|master)/]
if real_refname['master']
errors << "Master branch accepts only non-forwarded merge commits." if
!merge_commits[newrev] || !parent_commit[oldrev]
else
errors << "Version and Hotfix branches accept only fast-forward/rebased commits." if
merge_commits.empty?
errors << "Version and Hotfix branches must diverge from Master branch directly." if
!ancestor_branch[4, 6]['master']
end
errors
end
Even though the messages may be less neatly aligned here, I think it's an improvement that one can better see the conditions which should hold in each case. Note that I used the ruby idoms << instead of .push and [] instead of .match. I also left the Branch #{real_refname}: prefix out, it can be just as well in your error output loop if its always the same.
Also there is hardly a reason to rely on grep and sed when you have the power of ruby at hand.
As for the git part:
What you're trying to do is certainly possible, but I guess some try and error is needed. So I can't give you a working solution out of the hand. Some remarks though:
I think a better way to get a short symbolic ref in ruby is
`git symbolic-ref #{refname}\`[/[^\/]*$/].chomp
or even
`git symbolic-ref --short #{refname}`
you can try if that works more reliable than git rev-parse --abbrev-ref. Furthermore your variable real_refname is badly named. The 'real' ref name sounds like it would actually be the SHA1 hash. Probably short_refname would be better.
Since you're reading the refs from stdin I guess that you use a pre-receive git hook? But in this case you've clearly a bug, because there might be several branches updated in one push. You should either iterate over stdin or use the update hook
git show-branch is a porcelain command, i.e. it shouldn't be used for scripting because the output is meant for users. I think Junio did some pretty neat stuff in his pre-rebase.sample. Maybe you can get some ideas from there how to do it with plumbing commands.
I used to write even simple hooks in ruby, but I learned over the years that bash is also quite capable. So unless your hook gets really complex you might just start with bash.

get the latest commit where a file changed

My task at hand is to figure out, what is the commit id of the last commit, where a specific file changed. I'm using ruby / rugged. The only solution I came up with is to walk over all commits, search for the file in the tree associated with the commit for that file and compare that files oid with the oid of the file from the first (latest) commit:
def commit_oid commit, file
commit.tree.walk( :postorder ) { | root, obj |
return obj[ :oid ] if "#{root}#{obj[ :name ]}" == file
}
raise "\'#{file}\' not found in repository"
end
def find_last_commit file
johnny = Rugged::Walker.new( get_repository )
johnny.push get_repository.head.target
oid = commit_oid johnny.first, file
old_commit = johnny.first.oid
johnny.each do | commit |
new_oid = commit_oid commit, file
return old_commit if new_oid != oid
old_commit = commit.oid
end
old_commit
end
This works but seems to be quit complicated. There must be an easier ways to get the information, "what changed with a commit". Is there an easier, more straight forward way to accomplish the same?
Running $ git log <file> will give you a reverse chronological log of only commits that altered the given file. $ git whatchanged <file> will do the same, adding a line with details of the change (i.e. mode change, change type). It's great for visual purposes, but not so much for scripting.
If you want just the hash of the most recent commit, the following will work well: $ git rev-list --max-count 1 HEAD <file>

Ruby Grit: find commits between 2 branches

I would like to compare 2 branches and show the commits that exist in one but not the other. This works from command line git log --pretty=oneline branch_b ^branch_a --no-merges and gives me what I want but I would like to simulate the same thing in Grit to gain working with the object instead of strings. Is this possible in Grit?
I think something like this should do the trick:
require 'grit'
repo = Grit::Repo.new(".")
repo.commits_between("branch_b", "branch_a")
See http://grit.rubyforge.org/classes/Grit/Repo.html#M000227 for doc ( http://grit.rubyforge.org/ for full doc).
This will give you commit objects in an array though, it doesn't format the output like the git command does. Also, it doesn't exclude the merge commits.
In order to get formatted output, try something like this:
puts repo.commits_between("branch_b", "branch_a").map { |c| ["*", c.id_abbrev, c.authored_date.to_date, c.short_message] * " " }

Resources