Ruby Grit: find commits between 2 branches - ruby

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] * " " }

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

How to use git log --oneline in 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/

"git branch --merged <sha>" via Rugged libgit2 bindings?

Is there any way to get the same information as the native git command
git branch --merged <sha>
via the Rugged libgit2 bindings for Ruby?
What that git commit is doing is looking at each branch and checking whether the merge-base between the branch and the commit you've given (or HEAD if none) corresponds to the one of the branch.
If they match, it's merged; if they don't, it's not. You can do this loop in ruby fairly easily
repo.branches.each(:local) # look only at local branches
.map { |b|
tgt = b.resolve.target # look at what the branch is pointing to
# and check if the target commit is included in the history of HEAD
merged = repo.merge_base(repo.head.target, tgt) == tgt.oid
[b.name, merged]
} # this will give a list with the name and whether the branch is merged
.keep_if { |name, merged| merged } # keep only the ones which are merged
.map(&:first) # get the name
You could have an merged_list << b.name if merged in the first block and make it hang off of the each, but I like composing streams of data.
You can also change whether to use :local, :remote or both for the branches depending on your need. And you can also change repo.head.target to whatever id you want to compare against.

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>

In ruby/grit, how do I get a list of files changed in a specific commit?

I want a list of files affected by a certain commit in git. Through the command line, I can do this with:
git show --pretty="format:" --name-only (sha)
But how can I do this through Grit in Ruby?
You can use your_commit.diffs which returns an array of Grit::Diff instances. Grit::Diff has a_path and b_path properties.
Some (untested) example code:
paths = [];
#commit.diffs.each do |diff|
paths += [diff.a_path, diff.b_path]
end
paths.uniq!
Since Grit's git module employs method_missing to shell out, you can also try:
grit.git.show({ :pretty => :format, :name_only => true}, sha)

Resources