Class: Diagrams::GitgraphDiagram
- Defined in:
- lib/diagrams/gitgraph_diagram.rb
Overview
Represents a Gitgraph diagram, tracking commits, branches, and their relationships.
Instance Attribute Summary collapse
-
#branches ⇒ Object
readonly
Returns the value of attribute branches.
-
#commit_order ⇒ Object
readonly
Returns the value of attribute commit_order.
-
#commits ⇒ Object
readonly
Returns the value of attribute commits.
-
#current_branch_name ⇒ Object
readonly
Returns the value of attribute current_branch_name.
Attributes inherited from Base
Class Method Summary collapse
-
.from_h(data_hash, version:, checksum:) ⇒ GitgraphDiagram
Class method to create a GitgraphDiagram from a hash.
Instance Method Summary collapse
-
#branch(name:, start_commit_id: nil) ⇒ Elements::GitBranch
Creates a new branch pointing to a specific commit (or the current head) and switches the current context to the new branch.
-
#checkout(name:) ⇒ String
Switches the current context to an existing branch.
-
#cherry_pick(commit_id:, parent_override_id: nil) ⇒ Elements::GitCommit
Cherry-picks an existing commit onto the current branch.
-
#commit(id: nil, message: nil, tag: nil, type: :NORMAL) ⇒ Elements::GitCommit
Adds a commit to the current branch.
-
#identifiable_elements ⇒ Hash{Symbol => Array<Elements::GitCommit | Elements::GitBranch>}
Returns a hash mapping element types to their collections for diffing.
-
#initialize(version: 1) ⇒ GitgraphDiagram
constructor
Initializes a new GitgraphDiagram.
-
#merge(from_branch_name:, id: nil, tag: nil, type: :MERGE) ⇒ Elements::GitCommit
Merges the head of a specified branch into the current branch.
-
#to_h_content ⇒ Hash
Returns the specific content of the gitgraph diagram as a hash.
Methods inherited from Base
#diff, from_hash, from_json, #to_h, #to_json
Constructor Details
#initialize(version: 1) ⇒ GitgraphDiagram
Initializes a new GitgraphDiagram. Starts with a ‘master’ branch by default.
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
# File 'lib/diagrams/gitgraph_diagram.rb', line 14 def initialize(version: 1) super @commits = {} # Hash { commit_id => GitCommit } @branches = {} # Hash { branch_name => GitBranch } @commit_order = [] # Array<String> - IDs of commits in order of creation/operation @current_branch_name = 'master' # Initialize main branch conceptually. Its start/head commit will be set by the first commit. # We need a placeholder start_commit_id; using a special value or handling nil in GitBranch might be better. # For now, let's use a placeholder that signifies it's the root. # A better approach might be to create the branch *during* the first commit. Let's refine this. # --> Refinement: Don't create the branch object here. Create it during the first 'commit' or 'branch' operation. # Initialize @current_branch_name = 'master' conceptually. update_checksum! # Initial checksum for an empty graph end |
Instance Attribute Details
#branches ⇒ Object (readonly)
Returns the value of attribute branches.
8 9 10 |
# File 'lib/diagrams/gitgraph_diagram.rb', line 8 def branches @branches end |
#commit_order ⇒ Object (readonly)
Returns the value of attribute commit_order.
8 9 10 |
# File 'lib/diagrams/gitgraph_diagram.rb', line 8 def commit_order @commit_order end |
#commits ⇒ Object (readonly)
Returns the value of attribute commits.
8 9 10 |
# File 'lib/diagrams/gitgraph_diagram.rb', line 8 def commits @commits end |
#current_branch_name ⇒ Object (readonly)
Returns the value of attribute current_branch_name.
8 9 10 |
# File 'lib/diagrams/gitgraph_diagram.rb', line 8 def current_branch_name @current_branch_name end |
Class Method Details
.from_h(data_hash, version:, checksum:) ⇒ GitgraphDiagram
Class method to create a GitgraphDiagram from a hash.
285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 |
# File 'lib/diagrams/gitgraph_diagram.rb', line 285 def self.from_h(data_hash, version:, checksum:) diagram = new(version:) # Restore commits commits_data = data_hash[:commits] || data_hash['commits'] || [] commits_data.each do |commit_h| # Convert type back to symbol if it's a string commit_data = commit_h.transform_keys(&:to_sym) commit_data[:type] = commit_data[:type].to_sym if commit_data[:type].is_a?(String) commit = Elements::GitCommit.new(commit_data) diagram.commits[commit.id] = commit end # Restore branches branches_data = data_hash[:branches] || data_hash['branches'] || [] branches_data.each do |branch_h| branch = Elements::GitBranch.new(branch_h.transform_keys(&:to_sym)) diagram.branches[branch.name] = branch end # Restore commit order diagram.instance_variable_set(:@commit_order, data_hash[:commit_order] || data_hash['commit_order'] || []) # Restore current branch name diagram.instance_variable_set(:@current_branch_name, data_hash[:current_branch_name] || data_hash['current_branch_name'] || 'master') # Recalculate checksum after loading all data diagram.send(:update_checksum!) # Use send to call protected method from class scope # Optional: Verify checksum if provided if checksum && diagram.checksum != checksum warn "Checksum mismatch for loaded GitgraphDiagram (version: #{version}). Expected #{checksum}, got #{diagram.checksum}." end diagram end |
Instance Method Details
#branch(name:, start_commit_id: nil) ⇒ Elements::GitBranch
Creates a new branch pointing to a specific commit (or the current head) and switches the current context to the new branch.
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
# File 'lib/diagrams/gitgraph_diagram.rb', line 89 def branch(name:, start_commit_id: nil) raise ArgumentError, "Branch name '#{name}' already exists" if @branches.key?(name) effective_start_commit_id = start_commit_id || current_head_commit_id # Ensure there's a commit to branch from raise ArgumentError, 'Cannot create a branch before the first commit' unless effective_start_commit_id unless @commits.key?(effective_start_commit_id) raise ArgumentError, "Start commit ID '#{effective_start_commit_id}' does not exist" end new_branch = Elements::GitBranch.new( name:, # The new branch initially points to the commit it was created from start_commit_id: effective_start_commit_id, head_commit_id: effective_start_commit_id ) @branches[name] = new_branch @current_branch_name = name # Switch to the new branch update_checksum! new_branch end |
#checkout(name:) ⇒ String
Switches the current context to an existing branch.
121 122 123 124 125 126 127 128 |
# File 'lib/diagrams/gitgraph_diagram.rb', line 121 def checkout(name:) raise ArgumentError, "Branch '#{name}' does not exist. Cannot checkout." unless @branches.key?(name) @current_branch_name = name # NOTE: Checkout does not change the diagram structure itself (commits/branches), # so we do NOT update the checksum here. name end |
#cherry_pick(commit_id:, parent_override_id: nil) ⇒ Elements::GitCommit
Cherry-picks an existing commit onto the current branch. Creates a new commit on the current branch that mirrors the specified commit.
Basic implementation ignores parent_override_id for now
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 |
# File 'lib/diagrams/gitgraph_diagram.rb', line 202 def cherry_pick(commit_id:, parent_override_id: nil) unless @commits.key?(commit_id) raise ArgumentError, "Commit with ID '#{commit_id}' does not exist. Cannot cherry-pick." end source_commit = @commits[commit_id] current_branch_head_id = current_head_commit_id unless current_branch_head_id raise ArgumentError, "Current branch '#{@current_branch_name}' has no commits. Cannot cherry-pick onto it." end if source_commit.branch_name == @current_branch_name raise ArgumentError, "Commit '#{commit_id}' is already on the current branch '#{@current_branch_name}'. Cannot cherry-pick." end # More robust check: walk history? For now, simple branch name check. # TODO: Handle cherry-picking merge commits and parent_override_id if needed later. if source_commit.parent_ids.length > 1 && !parent_override_id warn "Cherry-picking a merge commit (#{commit_id}) without specifying a parent override is ambiguous. Picking first parent lineage by default." # Or raise ArgumentError: "Cherry-picking a merge commit requires specifying parent_override_id." end parent_ids = [current_branch_head_id] # Cherry-pick commit's parent is the current head new_commit_id = generate_commit_id(parent_ids, "Cherry-pick: #{source_commit. || source_commit.id}") if @commits.key?(new_commit_id) raise ArgumentError, "Generated commit ID '#{new_commit_id}' conflicts with existing commit." end cherry_pick_commit = Elements::GitCommit.new( id: new_commit_id, parent_ids:, branch_name: @current_branch_name, message: source_commit. || "Cherry-pick of #{source_commit.id}", # Copy message or use default tag: nil, # Cherry-picks usually don't copy tags directly type: :CHERRY_PICK, cherry_pick_source_id: commit_id # Link back to the original commit ) @commits[new_commit_id] = cherry_pick_commit @commit_order << new_commit_id # Update the head of the current branch current_branch = @branches[@current_branch_name] current_branch.attributes[:head_commit_id] = new_commit_id update_checksum! cherry_pick_commit end |
#commit(id: nil, message: nil, tag: nil, type: :NORMAL) ⇒ Elements::GitCommit
Adds a commit to the current branch. Handles the creation of the initial ‘master’ branch on the first commit.
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
# File 'lib/diagrams/gitgraph_diagram.rb', line 42 def commit(id: nil, message: nil, tag: nil, type: :NORMAL) parent_id = current_head_commit_id parent_ids = parent_id ? [parent_id] : [] commit_id = id || generate_commit_id(parent_ids, ) raise ArgumentError, "Commit with ID '#{commit_id}' already exists" if @commits.key?(commit_id) # Handle first commit: create the master branch if @commits.empty? && @current_branch_name == 'master' && !@branches.key?('master') # The first commit *is* the starting point of the master branch master_branch = Elements::GitBranch.new(name: 'master', start_commit_id: commit_id, head_commit_id: commit_id) @branches['master'] = master_branch elsif !@branches.key?(@current_branch_name) # This case shouldn't typically happen if branch/checkout is used correctly, # but defensively handle committing to a non-existent branch (other than initial master). raise ArgumentError, "Cannot commit: Branch '#{@current_branch_name}' does not exist." end new_commit = Elements::GitCommit.new( id: commit_id, parent_ids:, branch_name: @current_branch_name, message:, tag:, type: ) @commits[commit_id] = new_commit @commit_order << commit_id # Update the head of the current branch current_branch = @branches[@current_branch_name] current_branch.attributes[:head_commit_id] = commit_id # Update using Dry::Struct's way if needed, direct assign might work update_checksum! new_commit end |
#identifiable_elements ⇒ Hash{Symbol => Array<Elements::GitCommit | Elements::GitBranch>}
Returns a hash mapping element types to their collections for diffing.
273 274 275 276 277 278 |
# File 'lib/diagrams/gitgraph_diagram.rb', line 273 def identifiable_elements { commits: @commits.values, branches: @branches.values } end |
#merge(from_branch_name:, id: nil, tag: nil, type: :MERGE) ⇒ Elements::GitCommit
Merges the head of a specified branch into the current branch. Creates a merge commit on the current branch.
141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 |
# File 'lib/diagrams/gitgraph_diagram.rb', line 141 def merge(from_branch_name:, id: nil, tag: nil, type: :MERGE) if from_branch_name == @current_branch_name raise ArgumentError, "Cannot merge branch '#{from_branch_name}' into itself" end unless @branches.key?(from_branch_name) raise ArgumentError, "Branch '#{from_branch_name}' does not exist. Cannot merge." end unless @branches.key?(@current_branch_name) raise ArgumentError, "Current branch '#{@current_branch_name}' does not exist. Cannot merge." end target_branch = @branches[@current_branch_name] source_branch = @branches[from_branch_name] target_head_id = target_branch.head_commit_id source_head_id = source_branch.head_commit_id unless target_head_id raise ArgumentError, "Current branch '#{@current_branch_name}' has no commits to merge into." end raise ArgumentError, "Source branch '#{from_branch_name}' has no commits to merge from." unless source_head_id # Merge commit parents are the heads of the two branches being merged parent_ids = [target_head_id, source_head_id].sort # Sort for consistent checksumming/comparison merge_commit_id = id || generate_commit_id(parent_ids, "Merge branch '#{from_branch_name}' into #{@current_branch_name}") raise ArgumentError, "Commit with ID '#{merge_commit_id}' already exists" if @commits.key?(merge_commit_id) merge_commit = Elements::GitCommit.new( id: merge_commit_id, parent_ids:, branch_name: @current_branch_name, # Merge commit belongs to the target branch message: "Merge branch '#{from_branch_name}' into #{@current_branch_name}", # Default message tag:, type: # Use provided type, default :MERGE ) @commits[merge_commit_id] = merge_commit @commit_order << merge_commit_id # Update the head of the current (target) branch target_branch.attributes[:head_commit_id] = merge_commit_id update_checksum! merge_commit end |
#to_h_content ⇒ Hash
Returns the specific content of the gitgraph diagram as a hash.
260 261 262 263 264 265 266 267 268 269 |
# File 'lib/diagrams/gitgraph_diagram.rb', line 260 def to_h_content { commits: @commits.values.map(&:to_h), branches: @branches.values.map(&:to_h), commit_order: @commit_order, current_branch_name: @current_branch_name # Useful for resuming state? Maybe not needed in content hash. # Consider if current_branch_name should be part of the checksummable content. # For now, let's include it for potential deserialization needs. } end |