From e7ac748da9b14f07f5903e39f3f0911812c526f0 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Thu, 21 Jul 2016 03:37:16 -0700 Subject: [PATCH] Support non-linear series histories Document the format of a git-series merge commit, which has multiple git-series commits as parents in addition to the parents referencing gitlinks. (git-series does not yet generate such commits.) Modify revision walking in "git series log" to walk a git-series merge commit correctly. ("git series log -p" will warn that it cannot show diffs for such commits yet, as libgit2 doesn't support generating merge diffs yet.) --- INTERNALS.md | 48 ++++++++++++++++++++++++------------------ src/main.rs | 59 +++++++++++++++++++++++++++++++++++++--------------- 2 files changed, 70 insertions(+), 37 deletions(-) diff --git a/INTERNALS.md b/INTERNALS.md index 56c18a5aad55b549be4d57919f42c991e5fbe978..6b02812a3f4302c6deb2cdd1f134bcee9f4f65e8 100644 --- a/INTERNALS.md +++ b/INTERNALS.md @@ -39,28 +39,36 @@ In this documentation, a "git-series commit" refers to a commit corresponding to a version of an entire patch series, as distinguished from a commit corresponding to one patch within a patch series. -The first parent of each git-series commit always points to the previous -version of the patch series, if any. The remaining parents of each git-series -commit correspond to commits referenced as gitlinks (tree entries with mode -160000) within the commit's tree. This ensures that git can reach all of those -commits. (Note that git's traversal algorithm does not follow gitlink commits -within tree objects, so without these additional parent links, git would -consider these gitlink commits unreachable and discard them.) - -The second and subsequent parents of each git-series commit do not appear in -any particular order; do not assume that the `series` object or any other -gitlink appears at any particular position within the parents list. These -parents exist only to make commits reachable and transferable by git. Always -look up commits via named tree entries within the git-series commit's tree -object. +A git-series commit can have two types of parent commits: those connecting the +history of the patch series, and those referencing gitlink commits that also +appear in the git-series commit's tree. A git-series commit can have any +number of either type of parent, but all of the parents connecting the history +of the patch series will always appear before any of the parents referencing +gitlink commits. + +The parents connecting the history of the patch series, if any, point to +previous git-series commits representing previous versions of the patch series; +a git-series commit with more than one such parent represents a git-series +merge commit. The remaining parents of each git-series commit correspond to +commits referenced as gitlinks (tree entries with mode 160000) within the +commit's tree; this ensures that git can reach all of those commits. (Note +that git's traversal algorithm does not follow gitlink commits within tree +objects, so without these additional parent links, git would consider these +gitlink commits unreachable and discard them.) + +The parents of each git-series commit that reference gitlinks in that +git-series commit's tree do not appear in any particular order; do not assume +that the `series` object or any other gitlink appears at any particular +position within the parents list. These parents exist only to make commits +reachable and transferable by git. Always look up commits via named tree +entries within the git-series commit's tree object. In the root git-series commit, all the parent commits correspond to gitlinks -within the tree. This will not occur for any non-root commit of a git-series. -Algorithms trying to walk from a git-series commit to its root should detect -the root git-series commit by checking if the first parent appears in the -git-series commit's tree. (This does not require a recursive tree walk; the -first parent of the git-series root will always appear in the top-level tree -object.) +within the git-series commit's tree. This will not occur for any non-root +commit of a git-series. Algorithms trying to walk git-series commits should +filter out parents that appear in the git-series commit's tree. (This does not +require a recursive tree walk; the gitlinks within the git-series commit's tree +will appear in the top-level tree object.) git-series tree entries ----------------------- diff --git a/src/main.rs b/src/main.rs index 7101256f4b59114c744c4985a51729a5e151f366..dcee306f1b0f2d4b7f23531b9a970fdf00c41ad6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1202,41 +1202,66 @@ fn log(out: &mut Output, repo: &Repository, m: &ArgMatches) -> Result<()> { let color_commit = try!(out.get_color(&config, "diff", "commit", "yellow")); let mut revwalk = try!(repo.revwalk()); - revwalk.simplify_first_parent(); 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 mut hidden_ids = std::collections::HashSet::new(); + while let Some(oid) = revwalk.next() { + let oid = try!(oid); + let commit = try!(repo.find_commit(oid)); + let tree = try!(commit.tree()); + for parent_id in commit.parent_ids() { + if tree.iter().find(|entry| entry.id() == parent_id).is_some() { + try!(revwalk.hide(parent_id)); + hidden_ids.insert(parent_id); + } + } + } + + // set_sorting resets the revwalk + revwalk.set_sorting(git2::SORT_TOPOLOGICAL); + try!(revwalk.push_ref(SHEAD_REF)); + for id in hidden_ids { + try!(revwalk.hide(id)); + } + let show_diff = m.is_present("patch"); + let mut first = true; for oid in revwalk { + if first { + first = false; + } else { + try!(writeln!(out, "")); + } let oid = try!(oid); let commit = try!(repo.find_commit(oid)); - let tree = try!(commit.tree()); let author = commit.author(); - let first_parent_id = try!(commit.parent_id(0).map_err(|e| format!("Malformed series commit {}: {}", oid, e))); - let first_series_commit = tree.iter().find(|entry| entry.id() == first_parent_id).is_some(); - try!(writeln!(out, "{}", color_commit.paint(format!("commit {}", oid)))); try!(writeln!(out, "Author: {} <{}>", author.name().unwrap(), author.email().unwrap())); try!(writeln!(out, "Date: {}\n", date_822(author.when()))); for line in commit.message().unwrap().lines() { try!(writeln!(out, " {}", line)); } + if show_diff { - try!(writeln!(out, "")); - let parent_tree = if first_series_commit { - None - } else { - Some(try!(try!(repo.find_commit(first_parent_id)).tree())) - }; - let diff = try!(repo.diff_tree_to_tree(parent_tree.as_ref(), Some(&tree), None)); - try!(write_diff(out, &diff)); - } + let tree = try!(commit.tree()); + let parent_ids: Vec<_> = commit.parent_ids().take_while(|parent_id| tree.iter().find(|entry| &entry.id() == parent_id).is_none()).collect(); - if first_series_commit { - break; - } else { try!(writeln!(out, "")); + if parent_ids.len() > 1 { + try!(writeln!(out, "(Diffs of series merge commits not yet supported)")); + } else { + let parent_tree = if parent_ids.len() == 0 { + None + } else { + Some(try!(try!(repo.find_commit(parent_ids[0])).tree())) + }; + let diff = try!(repo.diff_tree_to_tree(parent_tree.as_ref(), Some(&tree), None)); + try!(write_diff(out, &diff)); + } } }