File: repository.go

package info (click to toggle)
gitbatch 0.6.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 644 kB
  • sloc: makefile: 5; sh: 1
file content (221 lines) | stat: -rw-r--r-- 5,935 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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
79
80
81
82
83
84
85
86
87
88
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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
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
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
package git

import (
	"os"
	"os/exec"
	"sync"
	"time"

	"github.com/go-git/go-git/v5"
)

// Reference is the interface for commits, remotes and branches
type Reference interface {
	Next() *Reference
	Previous() *Reference
}

// Repository is the main entity of the application. The repository name is
// actually the name of its folder in the host's filesystem. It holds the go-git
// repository entity along with critic entities such as remote/branches and commits
type Repository struct {
	RepoID   string
	Name     string
	AbsPath  string
	ModTime  time.Time
	Repo     git.Repository
	Branches []*Branch
	Remotes  []*Remote
	Stasheds []*StashedItem
	State    *RepositoryState

	mutex     *sync.RWMutex
	listeners map[string][]RepositoryListener
}

// RepositoryState is the current pointers of a repository
type RepositoryState struct {
	workStatus WorkStatus
	Branch     *Branch
	Remote     *Remote
	Message    string
}

// RepositoryListener is a type for listeners
type RepositoryListener func(event *RepositoryEvent) error

// RepositoryEvent is used to transfer event-related data.
// It is passed to listeners when Publish() is called
type RepositoryEvent struct {
	Name string
	Data interface{}
}

// WorkStatus is the state of the repository for an operation
type WorkStatus struct {
	Status uint8
	Ready  bool
}

var (
	// Available implies repo is ready for the operation
	Available = WorkStatus{Status: 0, Ready: true}
	// Queued means repo is queued for a operation
	Queued = WorkStatus{Status: 1, Ready: false}
	// Working means an operation is just started for this repository
	Working = WorkStatus{Status: 2, Ready: false}
	// Paused is expected when a user interaction is required
	Paused = WorkStatus{Status: 3, Ready: true}
	// Success is the expected outcome of the operation
	Success = WorkStatus{Status: 4, Ready: true}
	// Fail is the unexpected outcome of the operation
	Fail = WorkStatus{Status: 5, Ready: false}
)

const (
	// RepositoryUpdated defines the topic for an updated repository.
	RepositoryUpdated = "repository.updated"
	// BranchUpdated defines the topic for an updated branch.
	BranchUpdated = "branch.updated"
)

// FastInitializeRepo initializes a Repository struct without its belongings.
func FastInitializeRepo(dir string) (r *Repository, err error) {
	f, err := os.Open(dir)
	if err != nil {
		return nil, err
	}
	defer f.Close()
	// get status of the file
	fstat, _ := f.Stat()
	rp, err := git.PlainOpen(dir)
	if err != nil {
		return nil, err
	}
	// initialize Repository with minimum viable fields
	r = &Repository{RepoID: RandomString(8),
		Name:    fstat.Name(),
		AbsPath: dir,
		ModTime: fstat.ModTime(),
		Repo:    *rp,
		State: &RepositoryState{
			workStatus: Available,
			Message:    "",
		},
		mutex:     &sync.RWMutex{},
		listeners: make(map[string][]RepositoryListener),
	}
	return r, nil
}

// InitializeRepo initializes a Repository struct with its belongings.
func InitializeRepo(dir string) (r *Repository, err error) {
	r, err = FastInitializeRepo(dir)
	if err != nil {
		return nil, err
	}
	// need nothing extra but loading additional components
	return r, r.loadComponents(true)
}

// loadComponents initializes the fields of a repository such as branches,
// remotes, commits etc. If reset, reload commit, remote pointers too
func (r *Repository) loadComponents(reset bool) error {
	if err := r.initRemotes(); err != nil {
		return err
	}

	if err := r.initBranches(); err != nil {
		return err
	}

	if err := r.SyncRemoteAndBranch(r.State.Branch); err != nil {
		return err
	}

	return r.loadStashedItems()
}

// Refresh the belongings of a repository, this function is called right after
// fetch/pull/merge operations
func (r *Repository) Refresh() error {
	var err error
	// error can be ignored since the file already exists when app is loading
	// if the Repository is only fast initialized, no need to refresh because
	// it won't contain its belongings
	if r.State.Branch == nil {
		return nil
	}
	file, _ := os.Open(r.AbsPath)
	fstat, _ := file.Stat()
	// re-initialize the go-git repository struct after supposed update
	rp, err := git.PlainOpen(r.AbsPath)
	if err != nil {
		return err
	}
	r.Repo = *rp
	// modification date may be changed
	r.ModTime = fstat.ModTime()
	if err := r.loadComponents(false); err != nil {
		return err
	}
	// we could send an event data but we don't need for this topic
	return r.Publish(RepositoryUpdated, nil)
}

// On adds new listener.
// listener is a callback function that will be called when event emits
func (r *Repository) On(event string, listener RepositoryListener) {
	r.mutex.Lock()
	defer r.mutex.Unlock()
	// add listener to the specific event topic
	r.listeners[event] = append(r.listeners[event], listener)
}

// Publish publishes the data to a certain event by its name.
func (r *Repository) Publish(eventName string, data interface{}) error {
	r.mutex.RLock()
	defer r.mutex.RUnlock()
	// let's find listeners for this event topic
	listeners, ok := r.listeners[eventName]
	if !ok {
		return nil
	}
	// now notify the listeners and channel the data
	for i := range listeners {
		event := &RepositoryEvent{
			Name: eventName,
			Data: data,
		}
		if err := listeners[i](event); err != nil {
			return err
		}
	}
	return nil
}

// WorkStatus returns the state of the repository such as queued, failed etc.
func (r *Repository) WorkStatus() WorkStatus {
	return r.State.workStatus
}

// SetWorkStatus sets the state of repository and sends repository updated event
func (r *Repository) SetWorkStatus(ws WorkStatus) {
	r.State.workStatus = ws
	// we could send an event data but we don't need for this topic
	_ = r.Publish(RepositoryUpdated, nil)
}

func (r *Repository) String() string {
	return r.Name
}

func Create(dir string) (*Repository, error) {
	cmd := exec.Command("git", "init")
	cmd.Dir = dir
	_, err := cmd.CombinedOutput()
	if err != nil {
		return nil, err
	}
	return InitializeRepo(dir)
}