/* * This file is part of gitg * * Copyright (C) 2012 - Jesse van den Kieboom * * gitg is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * gitg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with gitg. If not, see . */ namespace Gitg { public class Lanes : Object { public int inactive_max { get; set; default = 30; } public int inactive_collapse { get; set; default = 10; } public int inactive_gap { get; set; default = 10; } public bool inactive_enabled { get; set; default = true; } private SList d_previous; private Gee.LinkedList d_lanes; private HashTable d_collapsed; private Gee.HashSet? d_roots; class LaneContainer { public Lane lane; public int inactive; public Ggit.OId? from; public Ggit.OId? to; public LaneContainer.with_color(Ggit.OId? from, Ggit.OId? to, Color? color) { this.from = from; this.to = to; this.lane = new Lane.with_color(color); this.inactive = 0; } public LaneContainer(Ggit.OId? from, Ggit.OId? to) { this.with_color(from, to, null); } public void next(int index) { var hidden = is_hidden; lane = lane.copy(); lane.tag = LaneTag.NONE; lane.from = new SList(); if (!hidden) { lane.from.prepend(index); } is_hidden = hidden; if (to != null && inactive >= 0) { ++inactive; } } public bool is_hidden { get { return (lane.tag & LaneTag.HIDDEN) != 0; } set { if (value) { lane.tag |= LaneTag.HIDDEN; } else { lane.tag &= ~LaneTag.HIDDEN; } } } } [Compact] class CollapsedLane { public Color color; public uint index; public Ggit.OId? from; public Ggit.OId? to; public CollapsedLane(LaneContainer container) { color = container.lane.color; from = container.from; to = container.to; } } public Lanes() { d_collapsed = new HashTable(Ggit.OId.hash, Ggit.OId.equal); var settings = new Settings("org.gnome.gitg.preferences.history"); settings.bind("collapse-inactive-lanes-enabled", this, "inactive-enabled", SettingsBindFlags.GET | SettingsBindFlags.SET); settings.bind("collapse-inactive-lanes", this, "inactive-collapse", SettingsBindFlags.GET | SettingsBindFlags.SET); reset(); } public void reset(Ggit.OId[]? reserved = null, Gee.HashSet? roots = null) { d_lanes = new Gee.LinkedList(); d_roots = roots; Color.reset(); if (reserved != null) { foreach (var r in reserved) { var ct = new LaneContainer(null, r); ct.inactive = -1; ct.is_hidden = true; d_lanes.add(ct); } } d_collapsed.remove_all(); d_previous = new SList(); } public bool next(Commit next, out SList lanes, out int nextpos) { var myoid = next.get_id(); if (inactive_enabled) { collapse_lanes(); expand_lanes(next); } LaneContainer? mylane = find_lane_by_oid(myoid, out nextpos); if (mylane == null && d_roots != null && !d_roots.contains(myoid)) { lanes = null; return false; } if (mylane == null) { // there is no lane reserved for this commit, add a new lane mylane = new LaneContainer(myoid, null); d_lanes.add(mylane); nextpos = (int)d_lanes.size - 1; } else { // copy the color here because the commit is a new stop mylane.lane.color = mylane.lane.color.copy(); mylane.to = null; mylane.from = next.get_id(); if (mylane.is_hidden && d_roots != null && d_roots.contains(myoid)) { mylane.is_hidden = false; mylane.lane.from = new SList(); } if (mylane.inactive >= 0) { mylane.inactive = 0; } } var hidden = mylane.is_hidden; lanes = lanes_list(); prepare_lanes(next, nextpos, hidden); return !hidden; } private void prepare_lanes(Commit next, int pos, bool hidden) { var parents = next.get_parents(); var myoid = next.get_id(); if (!hidden) { init_next_layer(); } var mylane = d_lanes[pos]; for (uint i = 0; i < parents.size; ++i) { int lnpos; var poid = parents.get_id(i); var container = find_lane_by_oid(poid, out lnpos); if (container != null) { // there is already a lane for this parent. This means that // we add pos as a merge for the lane. if (i == 0 && pos < lnpos) { // we are at the mainline of a merge, and this parent has // already been assigned to an existing lane, if our // lane's pos is smaller, then the this parent should be in // our lane instead. mylane.to = poid; mylane.from = myoid; if (!container.is_hidden) { mylane.lane.from.append(lnpos); mylane.is_hidden = false; } mylane.lane.color = mylane.lane.color.copy(); if (mylane.inactive >= 0) { mylane.inactive = 0; } d_lanes.remove(container); } else { container.from = myoid; if (!hidden) { container.lane.from.append(pos); } container.lane.color = container.lane.color.copy(); if (!hidden) { container.is_hidden = false; } if (container.inactive >= 0) { container.inactive = 0; } } continue; } else if (mylane != null && mylane.to == null) { // there is no parent yet which can proceed on the current // commit lane, so set it now mylane.to = poid; mylane.lane.color = mylane.lane.color.copy(); } else if (!hidden) { // generate a new lane for this parent var newlane = new LaneContainer(myoid, poid); newlane.lane.from.prepend(pos); d_lanes.add(newlane); } } if (mylane != null && mylane.to == null) { // remove current lane if no longer needed (i.e. merged) d_lanes.remove(mylane); } // store new commit in track list if (d_previous.length() == inactive_collapse + inactive_gap + 1) { d_previous.delete_link(d_previous.last()); } d_previous.prepend(next); } private void add_collapsed(LaneContainer container, int index) { var collapsed = new CollapsedLane(container); collapsed.index = index; d_collapsed.insert(container.to, (owned)collapsed); } private void collapse_lane(LaneContainer container, int index) { add_collapsed(container, index); unowned SList item = d_previous; while (item != null) { var commit = item.data; unowned SList lns = commit.get_lanes(); if (lns != null) { unowned Lane lane = lns.nth_data(index); if (item.next != null) { var newindex = lane.from.data; lns = commit.remove_lane(lane); if (item.next.next != null) { update_merge_indices(lns, newindex, -1); } var mylane = commit.mylane; if (mylane > index) { --commit.mylane; } index = newindex; } else { lane.tag |= LaneTag.END; lane.boundary_id = container.to; } } item = item.next; } } private void collapse_lanes() { int index = 0; var iter = d_lanes.iterator(); while (iter.next()) { var container = iter.get(); if (container.inactive != inactive_max + inactive_gap) { ++index; continue; } collapse_lane(container, container.lane.from.data); update_current_lane_merge_indices(index, -1); iter.remove(); } } private int ensure_correct_index(Commit commit, int index) { var len = commit.get_lanes().length(); if (index > len) { return (int)len; } else { return index; } } private void update_lane_merge_indices(SList from, int index, int direction) { while (from != null) { int idx = from.data; if (idx > index || (direction > 0 && idx == index)) { from.data = idx + direction; } from = from.next; } } private void update_merge_indices(SList lanes, int index, int direction) { foreach (unowned Lane lane in lanes) { update_lane_merge_indices(lane.from, index, direction); } } private void update_current_lane_merge_indices(int index, int direction) { foreach (var container in d_lanes) { update_lane_merge_indices(container.lane.from, index, direction); } } private void expand_lane(CollapsedLane lane) { var index = lane.index; var ln = new Lane.with_color(lane.color); var len = d_lanes.size; if (index > len) { index = len; } var next = ensure_correct_index(d_previous.data, (int)index); var container = new LaneContainer.with_color(lane.from, lane.to, lane.color); update_current_lane_merge_indices((int)index, 1); container.lane.from.prepend(next); d_lanes.insert((int)index, container); index = next; uint cnt = 0; unowned SList ptr = d_previous; while (ptr != null) { var commit = ptr.data; if (cnt == inactive_collapse) { break; } // Insert new lane at the index Lane copy = ln.copy(); unowned SList lns = commit.get_lanes(); if (ptr.next == null || cnt + 1 == inactive_collapse) { copy.boundary_id = lane.from; copy.tag |= LaneTag.START; } else { next = ensure_correct_index(ptr.next.data, (int)index); copy.from.prepend(next); update_merge_indices(lns, (int)index, 1); } commit.insert_lane(copy, (int)index); var mylane = commit.mylane; if (mylane >= index) { ++commit.mylane; } index = next; ++cnt; ptr = ptr.next; } } private void expand_lane_from_oid(Ggit.OId id) { unowned CollapsedLane? collapsed = d_collapsed.lookup(id); if (collapsed != null) { expand_lane(collapsed); d_collapsed.remove(id); } } private void expand_lanes(Commit commit) { expand_lane_from_oid(commit.get_id()); var parents = commit.get_parents(); for (uint i = 0; i < parents.size; ++i) { expand_lane_from_oid(parents.get_id(i)); } } private void init_next_layer() { int index = 0; foreach (var container in d_lanes) { container.next(index++); } } private LaneContainer? find_lane_by_oid(Ggit.OId id, out int pos) { int p = 0; foreach (var container in d_lanes) { if (container != null && id.equal(container.to)) { pos = p; return container; } ++p; } pos = -1; return null; } private SList lanes_list() { var ret = new SList(); foreach (var container in d_lanes) { ret.prepend(container.lane.copy()); } ret.reverse(); return ret; } } } // ex:set ts=4 noet