log: Walk series commits by hand, to allow pruning while walking

Josh Triplett created

Current versions of libgit2 no longer allow hiding commits while
walking.  With those versions, the revwalk would walk the entire project
history, not just the series history, taking seconds or minutes on large
repositories.  Work around that by doing the initial walk of series
commits by hand rather than with a revwalk.  The second walk still uses
a revwalk, for topological sorting.

Change summary

src/main.rs | 18 ++++++++----------
1 file changed, 8 insertions(+), 10 deletions(-)

Detailed changes

src/main.rs 🔗

@@ -1371,27 +1371,25 @@ fn log(out: &mut Output, repo: &Repository, m: &ArgMatches) -> Result<()> {
     try!(out.auto_pager(&config, "log", true));
     let diffcolors = try!(DiffColors::new(out, &config));
 
-    let mut revwalk = try!(repo.revwalk());
-    try!(revwalk.push_ref(SHEAD_REF));
-
-    // Walk once before sorting, to find all the commits to hide. Revwalk doesn't support hiding on
-    // the fly when sorted.
+    let shead_id = try!(repo.refname_to_id(SHEAD_REF));
     let mut hidden_ids = std::collections::HashSet::new();
-    while let Some(oid) = revwalk.next() {
-        let oid = try!(oid);
+    let mut commit_stack = Vec::new();
+    commit_stack.push(shead_id);
+    while let Some(oid) = commit_stack.pop() {
         let commit = try!(repo.find_commit(oid));
         let tree = try!(commit.tree());
         for parent_id in commit.parent_ids() {
             if tree.get_id(parent_id).is_some() {
-                try!(revwalk.hide(parent_id));
                 hidden_ids.insert(parent_id);
+            } else {
+                commit_stack.push(parent_id);
             }
         }
     }
 
-    // set_sorting resets the revwalk
+    let mut revwalk = try!(repo.revwalk());
     revwalk.set_sorting(git2::SORT_TOPOLOGICAL);
-    try!(revwalk.push_ref(SHEAD_REF));
+    try!(revwalk.push(shead_id));
     for id in hidden_ids {
         try!(revwalk.hide(id));
     }