/* * The MIT License * * Copyright (c) 2014 GitHub, Inc * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include "rugged.h" extern VALUE rb_mRugged; extern VALUE rb_cRuggedObject; VALUE rb_cRuggedWalker; static void rb_git_walk__free(git_revwalk *walk) { git_revwalk_free(walk); } VALUE rugged_walker_new(VALUE klass, VALUE owner, git_revwalk *walk) { VALUE rb_walk = Data_Wrap_Struct(klass, NULL, &rb_git_walk__free, walk); rugged_set_owner(rb_walk, owner); return rb_walk; } static void push_commit_oid(git_revwalk *walk, const git_oid *oid, int hide) { int error; if (hide) error = git_revwalk_hide(walk, oid); else error = git_revwalk_push(walk, oid); rugged_exception_check(error); } static void push_commit_ref(git_revwalk *walk, const char *ref, int hide) { int error; if (hide) error = git_revwalk_hide_ref(walk, ref); else error = git_revwalk_push_ref(walk, ref); rugged_exception_check(error); } static void push_commit_1(git_revwalk *walk, VALUE rb_commit, int hide) { if (rb_obj_is_kind_of(rb_commit, rb_cRuggedObject)) { git_object *object; Data_Get_Struct(rb_commit, git_object, object); push_commit_oid(walk, git_object_id(object), hide); return; } Check_Type(rb_commit, T_STRING); if (RSTRING_LEN(rb_commit) == 40) { git_oid commit_oid; if (git_oid_fromstr(&commit_oid, RSTRING_PTR(rb_commit)) == 0) { push_commit_oid(walk, &commit_oid, hide); return; } } push_commit_ref(walk, StringValueCStr(rb_commit), hide); } static void push_commit(git_revwalk *walk, VALUE rb_commit, int hide) { if (rb_type(rb_commit) == T_ARRAY) { long i; for (i = 0; i < RARRAY_LEN(rb_commit); ++i) push_commit_1(walk, rb_ary_entry(rb_commit, i), hide); return; } push_commit_1(walk, rb_commit, hide); } /* * call-seq: * Walker.new(repository) -> walker * * Create a new +Walker+ instance able to walk commits found * in +repository+, which is a Rugged::Repository instance. */ static VALUE rb_git_walker_new(VALUE klass, VALUE rb_repo) { git_repository *repo; git_revwalk *walk; int error; Data_Get_Struct(rb_repo, git_repository, repo); error = git_revwalk_new(&walk, repo); rugged_exception_check(error); return rugged_walker_new(klass, rb_repo, walk);; } /* * call-seq: * walker.push(commit) -> nil * * Push one new +commit+ to start the walk from. +commit+ must be a * +String+ with the OID of a commit in the repository, or a Rugged::Commit * instance. * * More than one commit may be pushed to the walker (to walk several * branches simulataneously). * * Duplicate pushed commits will be ignored; at least one commit must have been * pushed as a starting point before the walk can begin. * * walker.push("92b22bbcb37caf4f6f53d30292169e84f5e4283b") */ static VALUE rb_git_walker_push(VALUE self, VALUE rb_commit) { git_revwalk *walk; Data_Get_Struct(self, git_revwalk, walk); push_commit(walk, rb_commit, 0); return Qnil; } static VALUE rb_git_walker_push_range(VALUE self, VALUE range) { git_revwalk *walk; Data_Get_Struct(self, git_revwalk, walk); rugged_exception_check(git_revwalk_push_range(walk, StringValueCStr(range))); return Qnil; } /* * call-seq: * walker.hide(commit) -> nil * * Hide the given +commit+ (and all its parents) from the * output in the revision walk. */ static VALUE rb_git_walker_hide(VALUE self, VALUE rb_commit) { git_revwalk *walk; Data_Get_Struct(self, git_revwalk, walk); push_commit(walk, rb_commit, 1); return Qnil; } /* * call-seq: * walker.sorting(sort_mode) -> nil * * Change the sorting mode for the revision walk. * * This will cause +walker+ to be reset. */ static VALUE rb_git_walker_sorting(VALUE self, VALUE ruby_sort_mode) { git_revwalk *walk; Data_Get_Struct(self, git_revwalk, walk); git_revwalk_sorting(walk, FIX2INT(ruby_sort_mode)); return Qnil; } /* * call-seq: * walker.simplify_first_parent() -> nil * * Simplify the walk to the first parent of each commit. */ static VALUE rb_git_walker_simplify_first_parent(VALUE self) { git_revwalk *walk; Data_Get_Struct(self, git_revwalk, walk); git_revwalk_simplify_first_parent(walk); return Qnil; } /* * call-seq: * walker.count -> Fixnum * * Returns the amount of objects a walker iterated over. If an argument or * block is given this method delegates to +Enumerable#count+. */ static VALUE rb_git_walker_count(int argc, VALUE *argv, VALUE self) { git_revwalk *walk; git_oid commit_oid; int error = 0; uint64_t count = 0; if (argc > 0 || rb_block_given_p()) return rb_call_super(argc, argv); Data_Get_Struct(self, git_revwalk, walk); while (((error = git_revwalk_next(&commit_oid, walk)) == 0) && ++count != UINT64_MAX); if (error != GIT_ITEROVER) rugged_exception_check(error); return ULONG2NUM(count); } /* * call-seq: * walker.reset -> nil * * Remove all pushed and hidden commits and reset the +walker+ * back into a blank state. */ static VALUE rb_git_walker_reset(VALUE self) { git_revwalk *walk; Data_Get_Struct(self, git_revwalk, walk); git_revwalk_reset(walk); return Qnil; } struct walk_options { VALUE rb_owner; VALUE rb_options; git_repository *repo; git_revwalk *walk; int oid_only; uint64_t offset, limit; }; static void load_walk_limits(struct walk_options *w, VALUE rb_options) { VALUE rb_value = rb_hash_lookup(rb_options, CSTR2SYM("offset")); if (!NIL_P(rb_value)) { Check_Type(rb_value, T_FIXNUM); w->offset = FIX2ULONG(rb_value); } rb_value = rb_hash_lookup(rb_options, CSTR2SYM("limit")); if (!NIL_P(rb_value)) { Check_Type(rb_value, T_FIXNUM); w->limit = FIX2ULONG(rb_value); } } static VALUE load_all_options(VALUE _payload) { struct walk_options *w = (struct walk_options *)_payload; VALUE rb_options = w->rb_options; VALUE rb_show, rb_hide, rb_sort, rb_simp, rb_oid_only; load_walk_limits(w, rb_options); rb_sort = rb_hash_lookup(rb_options, CSTR2SYM("sort")); if (!NIL_P(rb_sort)) { Check_Type(rb_sort, T_FIXNUM); git_revwalk_sorting(w->walk, FIX2INT(rb_sort)); } rb_show = rb_hash_lookup(rb_options, CSTR2SYM("show")); if (!NIL_P(rb_show)) { push_commit(w->walk, rb_show, 0); } rb_hide = rb_hash_lookup(rb_options, CSTR2SYM("hide")); if (!NIL_P(rb_hide)) { push_commit(w->walk, rb_hide, 1); } rb_simp = rb_hash_lookup(rb_options, CSTR2SYM("simplify")); if (RTEST(rb_simp)) { git_revwalk_simplify_first_parent(w->walk); } rb_oid_only = rb_hash_lookup(rb_options, CSTR2SYM("oid_only")); if (RTEST(rb_oid_only)) { w->oid_only = 1; } return Qnil; } static VALUE do_walk(VALUE _payload) { struct walk_options *w = (struct walk_options *)_payload; int error; git_oid commit_oid; while ((error = git_revwalk_next(&commit_oid, w->walk)) == 0) { if (w->offset > 0) { w->offset--; continue; } if (w->oid_only) { rb_yield(rugged_create_oid(&commit_oid)); } else { git_commit *commit; error = git_commit_lookup(&commit, w->repo, &commit_oid); rugged_exception_check(error); rb_yield( rugged_object_new(w->rb_owner, (git_object *)commit) ); } if (--w->limit == 0) break; } if (error != GIT_ITEROVER) rugged_exception_check(error); return Qnil; } /* * call-seq: * Rugged::Walker.walk(repo, options={}) { |commit| block } * * Create a Walker object, initialize it with the given options * and perform a walk on the repository; the lifetime of the * walker is bound to the call and it is immediately cleaned * up after the walk is over. * * The following options are available: * * - +sort+: Sorting mode for the walk (or an OR combination * of several sorting modes). * * - +show+: Tips of the repository that will be walked; * this is necessary for the walk to yield any results. * A tip can be a 40-char object ID, an existing +Rugged::Commit+ * object, a reference, or an +Array+ of several of these * (if you'd like to walk several tips in parallel). * * - +hide+: Same as +show+, but hides the given tips instead * so they don't appear on the walk. * * - +oid_only+: if +true+, the walker will yield 40-char OIDs * for each commit, instead of real +Rugged::Commit+ objects. * Defaults to +false+. * * - +simplify+: if +true+, the walk will be simplified * to the first parent of each commit. * * Example: * * Rugged::Walker.walk(repo, * show: "92b22bbcb37caf4f6f53d30292169e84f5e4283b", * sort: Rugged::SORT_DATE|Rugged::SORT_TOPO, * oid_only: true) do |commit_oid| * puts commit_oid * end * * generates: * * 92b22bbcb37caf4f6f53d30292169e84f5e4283b * 6b750d5800439b502de669465b385e5f469c78b6 * ef9207141549f4ffcd3c4597e270d32e10d0a6bc * cb75e05f0f8ac3407fb3bd0ebd5ff07573b16c9f * ... */ static VALUE rb_git_walk(int argc, VALUE *argv, VALUE self) { VALUE rb_repo, rb_options; struct walk_options w; int exception = 0; rb_scan_args(argc, argv, "10:", &rb_repo, &rb_options); if (!rb_block_given_p()) { ID iter_method = ID2SYM(rb_intern("walk")); return rb_funcall(self, rb_intern("to_enum"), 3, iter_method, rb_repo, rb_options); } Data_Get_Struct(rb_repo, git_repository, w.repo); rugged_exception_check(git_revwalk_new(&w.walk, w.repo)); w.rb_owner = rb_repo; w.rb_options = rb_options; w.oid_only = 0; w.offset = 0; w.limit = UINT64_MAX; if (!NIL_P(w.rb_options)) rb_protect(load_all_options, (VALUE)&w, &exception); if (!exception) rb_protect(do_walk, (VALUE)&w, &exception); git_revwalk_free(w.walk); if (exception) rb_jump_tag(exception); return Qnil; } static VALUE rb_git_walk_with_opts(int argc, VALUE *argv, VALUE self, int oid_only) { VALUE rb_options; struct walk_options w; rb_scan_args(argc, argv, "01", &rb_options); if (!rb_block_given_p()) { ID iter_method = ID2SYM(rb_intern(oid_only ? "each_oid" : "each")); return rb_funcall(self, rb_intern("to_enum"), 2, iter_method, rb_options); } Data_Get_Struct(self, git_revwalk, w.walk); w.repo = git_revwalk_repository(w.walk); w.rb_owner = rugged_owner(self); w.rb_options = Qnil; w.oid_only = oid_only; w.offset = 0; w.limit = UINT64_MAX; if (!NIL_P(rb_options)) load_walk_limits(&w, rb_options); return do_walk((VALUE)&w); } /* * call-seq: * walker.each { |commit| block } * walker.each -> Iterator * * Perform the walk through the repository, yielding each * one of the commits found as a Rugged::Commit instance * to +block+. * * If no +block+ is given, an +Iterator+ will be returned. * * The walker must have been previously set-up before a walk can be performed * (i.e. at least one commit must have been pushed). * * walker.push("92b22bbcb37caf4f6f53d30292169e84f5e4283b") * walker.each { |commit| puts commit.oid } * * generates: * * 92b22bbcb37caf4f6f53d30292169e84f5e4283b * 6b750d5800439b502de669465b385e5f469c78b6 * ef9207141549f4ffcd3c4597e270d32e10d0a6bc * cb75e05f0f8ac3407fb3bd0ebd5ff07573b16c9f * ... */ static VALUE rb_git_walker_each(int argc, VALUE *argv, VALUE self) { return rb_git_walk_with_opts(argc, argv, self, 0); } /* * call-seq: * walker.each_oid { |commit| block } * walker.each_oid -> Iterator * * Perform the walk through the repository, yielding each * one of the commit oids found as a String * to +block+. * * If no +block+ is given, an +Iterator+ will be returned. * * The walker must have been previously set-up before a walk can be performed * (i.e. at least one commit must have been pushed). * * walker.push("92b22bbcb37caf4f6f53d30292169e84f5e4283b") * walker.each { |commit_oid| puts commit_oid } * * generates: * * 92b22bbcb37caf4f6f53d30292169e84f5e4283b * 6b750d5800439b502de669465b385e5f469c78b6 * ef9207141549f4ffcd3c4597e270d32e10d0a6bc * cb75e05f0f8ac3407fb3bd0ebd5ff07573b16c9f * ... */ static VALUE rb_git_walker_each_oid(int argc, VALUE *argv, VALUE self) { return rb_git_walk_with_opts(argc, argv, self, 1); } void Init_rugged_revwalk(void) { rb_cRuggedWalker = rb_define_class_under(rb_mRugged, "Walker", rb_cObject); rb_define_singleton_method(rb_cRuggedWalker, "new", rb_git_walker_new, 1); rb_define_singleton_method(rb_cRuggedWalker, "walk", rb_git_walk, -1); rb_define_method(rb_cRuggedWalker, "push", rb_git_walker_push, 1); rb_define_method(rb_cRuggedWalker, "push_range", rb_git_walker_push_range, 1); rb_define_method(rb_cRuggedWalker, "each", rb_git_walker_each, -1); rb_define_method(rb_cRuggedWalker, "each_oid", rb_git_walker_each_oid, -1); rb_define_method(rb_cRuggedWalker, "walk", rb_git_walker_each, -1); rb_define_method(rb_cRuggedWalker, "hide", rb_git_walker_hide, 1); rb_define_method(rb_cRuggedWalker, "reset", rb_git_walker_reset, 0); rb_define_method(rb_cRuggedWalker, "sorting", rb_git_walker_sorting, 1); rb_define_method(rb_cRuggedWalker, "simplify_first_parent", rb_git_walker_simplify_first_parent, 0); rb_define_method(rb_cRuggedWalker, "count", rb_git_walker_count, -1); }