A vibe coded tangled fork which supports pijul.
1package pijul
2
3import (
4 "bufio"
5 "bytes"
6 "fmt"
7 "strings"
8)
9
10// Channel represents a Pijul channel (analogous to a Git branch)
11type Channel struct {
12 Name string `json:"name"`
13 IsCurrent bool `json:"is_current,omitempty"`
14}
15
16// Channels returns the list of channels in the repository
17func (p *PijulRepo) Channels() ([]Channel, error) {
18 output, err := p.channelCmd()
19 if err != nil {
20 return nil, fmt.Errorf("listing channels: %w", err)
21 }
22
23 return parseChannelOutput(output)
24}
25
26// parseChannelOutput parses the output of pijul channel
27// Expected format:
28//
29// * main
30// feature-branch
31// another-branch
32func parseChannelOutput(output []byte) ([]Channel, error) {
33 var channels []Channel
34 scanner := bufio.NewScanner(bytes.NewReader(output))
35
36 for scanner.Scan() {
37 line := scanner.Text()
38 if strings.TrimSpace(line) == "" {
39 continue
40 }
41
42 isCurrent := strings.HasPrefix(line, "* ")
43 name := strings.TrimSpace(strings.TrimPrefix(line, "* "))
44 name = strings.TrimSpace(strings.TrimPrefix(name, " "))
45
46 if name != "" {
47 channels = append(channels, Channel{
48 Name: name,
49 IsCurrent: isCurrent,
50 })
51 }
52 }
53
54 return channels, scanner.Err()
55}
56
57// ChannelOptions options for channel listing
58type ChannelOptions struct {
59 Limit int
60 Offset int
61}
62
63// ChannelsWithOptions returns channels with pagination
64func (p *PijulRepo) ChannelsWithOptions(opts *ChannelOptions) ([]Channel, error) {
65 channels, err := p.Channels()
66 if err != nil {
67 return nil, err
68 }
69
70 if opts == nil {
71 return channels, nil
72 }
73
74 // Apply offset
75 if opts.Offset > 0 {
76 if opts.Offset >= len(channels) {
77 return []Channel{}, nil
78 }
79 channels = channels[opts.Offset:]
80 }
81
82 // Apply limit
83 if opts.Limit > 0 && opts.Limit < len(channels) {
84 channels = channels[:opts.Limit]
85 }
86
87 return channels, nil
88}
89
90// CreateChannel creates a new channel
91func (p *PijulRepo) CreateChannel(name string) error {
92 _, err := p.channelCmd("new", name)
93 return err
94}
95
96// DeleteChannel deletes a channel
97func (p *PijulRepo) DeleteChannel(name string) error {
98 _, err := p.channelCmd("delete", name)
99 return err
100}
101
102// RenameChannel renames a channel
103func (p *PijulRepo) RenameChannel(oldName, newName string) error {
104 _, err := p.channelCmd("rename", oldName, newName)
105 return err
106}
107
108// SwitchChannel switches to a different channel
109func (p *PijulRepo) SwitchChannel(name string) error {
110 _, err := p.channelCmd("switch", name)
111 if err != nil {
112 return fmt.Errorf("switching to channel %s: %w", name, err)
113 }
114 p.channelName = name
115 return nil
116}
117
118// CurrentChannelName returns the name of the current channel
119func (p *PijulRepo) CurrentChannelName() (string, error) {
120 channels, err := p.Channels()
121 if err != nil {
122 return "", err
123 }
124
125 for _, ch := range channels {
126 if ch.IsCurrent {
127 return ch.Name, nil
128 }
129 }
130
131 // If no current channel marked, default to "main"
132 return "main", nil
133}
134
135// ForkChannel creates a new channel from an existing one
136// This is equivalent to Git's "git checkout -b newbranch oldbranch"
137func (p *PijulRepo) ForkChannel(newName, fromChannel string) error {
138 args := []string{"fork", newName}
139 if fromChannel != "" {
140 args = append(args, "--channel", fromChannel)
141 }
142 _, err := p.channelCmd(args...)
143 return err
144}
145
146// ChannelExists checks if a channel exists
147func (p *PijulRepo) ChannelExists(name string) (bool, error) {
148 channels, err := p.Channels()
149 if err != nil {
150 return false, err
151 }
152
153 for _, ch := range channels {
154 if ch.Name == name {
155 return true, nil
156 }
157 }
158
159 return false, nil
160}