@@ -20,7 +20,7 @@ class CommitGraph:
2020 nodes : List [CommitNode ] = field (default_factory = list )
2121 index : Dict [str , int ] = field (default_factory = dict ) # sha -> row
2222
23- def build (self , commits : List [Dict [str , Any ]], refs : Dict [str , str ] | None = None ) -> None :
23+ def build (self , commits : List [Dict [str , Any ]], _refs : Dict [str , str ] | None = None ) -> None :
2424 """
2525 Build commit graph from topo-ordered commits.
2626 從 topo-order 的 commits 建立 commit graph。
@@ -38,40 +38,43 @@ def build(self, commits: List[Dict[str, Any]], refs: Dict[str, str] | None = Non
3838 self .index = {node .commit_sha : index for index , node in enumerate (self .nodes )}
3939 self ._assign_lanes ()
4040
41- def _assign_lanes (self ) -> None :
42- """
43- Assign lanes to commits, similar to `git log --graph`.
44- 分配 lanes,模擬 `git log --graph` 的效果。
45- """
46- active : Dict [int , str ] = {} # lane -> sha
47- free_lanes : List [int ] = []
41+ @staticmethod
42+ def _pick_lane (active : Dict [int , str ], free_lanes : List [int ], sha : str ) -> int :
43+ """選出 commit 應該配置的 lane / Pick the lane for this commit."""
44+ for lane , lane_sha in active .items ():
45+ if lane_sha == sha :
46+ return lane
47+ if free_lanes :
48+ return free_lanes .pop (0 )
49+ return 0 if not active else max (active .keys ()) + 1
4850
49- for node in self .nodes :
50- # Step 1: 找到 lane
51- lane_found = next ((lane for lane , sha in active .items () if sha == node .commit_sha ), None )
51+ @staticmethod
52+ def _assign_parent_lanes (active : Dict [int , str ], free_lanes : List [int ],
53+ node_lane : int , parent_shas : List [str ]) -> None :
54+ """把父節點放進 active lanes / Assign each parent to a lane in-place."""
55+ if not parent_shas :
56+ return
57+ active [node_lane ] = parent_shas [0 ]
58+ for parent in parent_shas [1 :]:
59+ lane = free_lanes .pop (0 ) if free_lanes else (max (active .keys ()) + 1 )
60+ active [lane ] = parent
5261
53- if lane_found is not None :
54- node .lane_index = lane_found
55- elif free_lanes :
56- node .lane_index = free_lanes .pop (0 )
57- else :
58- node .lane_index = 0 if not active else max (active .keys ()) + 1
62+ @staticmethod
63+ def _recompute_free_lanes (active : Dict [int , str ], free_lanes : List [int ]) -> List [int ]:
64+ """回傳更新後的 free lane 清單 / Return updated free lane list."""
65+ if not active :
66+ return free_lanes
67+ max_lane = max (active .keys ())
68+ used = set (active .keys ())
69+ all_lanes = set (range (max_lane + 1 ))
70+ return sorted (set (free_lanes ).union (all_lanes - used ))
5971
60- # Step 2: 更新 active
61- # 移除舊的 sha
72+ def _assign_lanes (self ) -> None :
73+ """分配 lanes,模擬 `git log --graph` / Assign lanes to commits, like `git log --graph`."""
74+ active : Dict [int , str ] = {}
75+ free_lanes : List [int ] = []
76+ for node in self .nodes :
77+ node .lane_index = self ._pick_lane (active , free_lanes , node .commit_sha )
6278 active = {lane : sha for lane , sha in active .items () if sha != node .commit_sha }
63-
64- # 父節點分配 lane
65- if node .parent_shas :
66- first_parent = node .parent_shas [0 ]
67- active [node .lane_index ] = first_parent
68- for p in node .parent_shas [1 :]:
69- pl = free_lanes .pop (0 ) if free_lanes else (max (active .keys ()) + 1 )
70- active [pl ] = p
71-
72- # Step 3: 更新 free_lanes
73- if active :
74- max_lane = max (active .keys ())
75- used = set (active .keys ())
76- all_lanes = set (range (max_lane + 1 ))
77- free_lanes = sorted (set (free_lanes ).union (all_lanes - used ))
79+ self ._assign_parent_lanes (active , free_lanes , node .lane_index , node .parent_shas )
80+ free_lanes = self ._recompute_free_lanes (active , free_lanes )
0 commit comments