git-module

git clone git://git.lin.moe/fork/git-module.git

  1package git
  2
  3import (
  4	"bytes"
  5	"context"
  6	"strings"
  7)
  8
  9// LsRemoteOptions contains arguments for listing references in a remote
 10// repository.
 11//
 12// Docs: https://git-scm.com/docs/git-ls-remote
 13type LsRemoteOptions struct {
 14	// Indicates whether include heads.
 15	Heads bool
 16	// Indicates whether include tags.
 17	Tags bool
 18	// Indicates whether to not show peeled tags or pseudo refs.
 19	Refs bool
 20	// The list of patterns to filter results.
 21	Patterns []string
 22	CommandOptions
 23}
 24
 25// LsRemote returns a list of references in the remote repository.
 26func LsRemote(ctx context.Context, url string, opts ...LsRemoteOptions) ([]*Reference, error) {
 27	var opt LsRemoteOptions
 28	if len(opts) > 0 {
 29		opt = opts[0]
 30	}
 31
 32	args := []string{"ls-remote", "--quiet"}
 33	if opt.Heads {
 34		args = append(args, "--heads")
 35	}
 36	if opt.Tags {
 37		args = append(args, "--tags")
 38	}
 39	if opt.Refs {
 40		args = append(args, "--refs")
 41	}
 42	args = append(args, "--end-of-options", url)
 43	if len(opt.Patterns) > 0 {
 44		args = append(args, opt.Patterns...)
 45	}
 46
 47	stdout, err := exec(ctx, "", args, opt.Envs)
 48	if err != nil {
 49		return nil, err
 50	}
 51
 52	lines := bytes.Split(stdout, []byte("\n"))
 53	refs := make([]*Reference, 0, len(lines))
 54	for i := range lines {
 55		fields := bytes.Fields(lines[i])
 56		if len(fields) < 2 {
 57			continue
 58		}
 59
 60		refs = append(refs, &Reference{
 61			ID:      string(fields[0]),
 62			Refspec: string(fields[1]),
 63		})
 64	}
 65	return refs, nil
 66}
 67
 68// IsURLAccessible returns true if given remote URL is accessible via Git. The
 69// caller should use context.WithTimeout to control the timeout.
 70func IsURLAccessible(ctx context.Context, url string) bool {
 71	_, err := LsRemote(ctx, url, LsRemoteOptions{
 72		Patterns: []string{"HEAD"},
 73	})
 74	return err == nil
 75}
 76
 77// RemoteAddOptions contains options to add a remote address.
 78//
 79// Docs: https://git-scm.com/docs/git-remote#Documentation/git-remote.txt-emaddem
 80type RemoteAddOptions struct {
 81	// Indicates whether to execute git fetch after the remote information is set
 82	// up.
 83	Fetch bool
 84	// Indicates whether to add remote as mirror with --mirror=fetch.
 85	MirrorFetch bool
 86	CommandOptions
 87}
 88
 89// RemoteAdd adds a new remote to the repository.
 90func (r *Repository) RemoteAdd(ctx context.Context, name, url string, opts ...RemoteAddOptions) error {
 91	var opt RemoteAddOptions
 92	if len(opts) > 0 {
 93		opt = opts[0]
 94	}
 95
 96	args := []string{"remote", "add"}
 97	if opt.Fetch {
 98		args = append(args, "-f")
 99	}
100	if opt.MirrorFetch {
101		args = append(args, "--mirror=fetch")
102	}
103	args = append(args, "--end-of-options", name, url)
104
105	_, err := exec(ctx, r.path, args, opt.Envs)
106	return err
107}
108
109// RemoteRemoveOptions contains arguments for removing a remote from the
110// repository.
111//
112// Docs: https://git-scm.com/docs/git-remote#Documentation/git-remote.txt-emremoveem
113type RemoteRemoveOptions struct {
114	CommandOptions
115}
116
117// RemoteRemove removes a remote from the repository.
118func (r *Repository) RemoteRemove(ctx context.Context, name string, opts ...RemoteRemoveOptions) error {
119	var opt RemoteRemoveOptions
120	if len(opts) > 0 {
121		opt = opts[0]
122	}
123
124	args := []string{"remote", "remove", "--end-of-options", name}
125	_, err := exec(ctx, r.path, args, opt.Envs)
126	if err != nil {
127		if strings.Contains(err.Error(), "error: No such remote") ||
128			strings.Contains(err.Error(), "fatal: No such remote") {
129			return ErrRemoteNotExist
130		}
131		return err
132	}
133	return nil
134}
135
136// RemotesOptions contains arguments for listing remotes of the repository.
137// /
138// Docs: https://git-scm.com/docs/git-remote#_commands
139type RemotesOptions struct {
140	CommandOptions
141}
142
143// Remotes lists remotes of the repository.
144func (r *Repository) Remotes(ctx context.Context, opts ...RemotesOptions) ([]string, error) {
145	var opt RemotesOptions
146	if len(opts) > 0 {
147		opt = opts[0]
148	}
149
150	args := []string{"remote", "--end-of-options"}
151	stdout, err := exec(ctx, r.path, args, opt.Envs)
152	if err != nil {
153		return nil, err
154	}
155
156	return bytesToStrings(stdout), nil
157}
158
159// RemoteGetURLOptions contains arguments for retrieving URL(s) of a remote of
160// the repository.
161//
162// Docs: https://git-scm.com/docs/git-remote#Documentation/git-remote.txt-emget-urlem
163type RemoteGetURLOptions struct {
164	// Indicates whether to get push URLs instead of fetch URLs.
165	Push bool
166	// Indicates whether to get all URLs, including lists that are not part of main
167	// URLs. This option is independent of the Push option.
168	All bool
169	CommandOptions
170}
171
172// RemoteGetURL retrieves URL(s) of a remote of the repository.
173func (r *Repository) RemoteGetURL(ctx context.Context, name string, opts ...RemoteGetURLOptions) ([]string, error) {
174	var opt RemoteGetURLOptions
175	if len(opts) > 0 {
176		opt = opts[0]
177	}
178
179	args := []string{"remote", "get-url"}
180	if opt.Push {
181		args = append(args, "--push")
182	}
183	if opt.All {
184		args = append(args, "--all")
185	}
186	args = append(args, "--end-of-options", name)
187
188	stdout, err := exec(ctx, r.path, args, opt.Envs)
189	if err != nil {
190		return nil, err
191	}
192	return bytesToStrings(stdout), nil
193}
194
195// RemoteSetURLOptions contains arguments for setting an URL of a remote of the
196// repository.
197//
198// Docs: https://git-scm.com/docs/git-remote#Documentation/git-remote.txt-emset-urlem
199type RemoteSetURLOptions struct {
200	// Indicates whether to get push URLs instead of fetch URLs.
201	Push bool
202	// The regex to match existing URLs to replace (instead of first).
203	Regex string
204	CommandOptions
205}
206
207// RemoteSetURL sets the first URL of the remote with given name of the
208// repository.
209func (r *Repository) RemoteSetURL(ctx context.Context, name, newurl string, opts ...RemoteSetURLOptions) error {
210	var opt RemoteSetURLOptions
211	if len(opts) > 0 {
212		opt = opts[0]
213	}
214
215	args := []string{"remote", "set-url"}
216	if opt.Push {
217		args = append(args, "--push")
218	}
219	args = append(args, "--end-of-options", name, newurl)
220	if opt.Regex != "" {
221		args = append(args, opt.Regex)
222	}
223
224	_, err := exec(ctx, r.path, args, opt.Envs)
225	if err != nil {
226		if strings.Contains(err.Error(), "No such URL found") {
227			return ErrURLNotExist
228		} else if strings.Contains(err.Error(), "No such remote") {
229			return ErrRemoteNotExist
230		}
231		return err
232	}
233	return nil
234}
235
236// RemoteSetURLAddOptions contains arguments for appending an URL to a remote
237// of the repository.
238//
239// Docs: https://git-scm.com/docs/git-remote#Documentation/git-remote.txt-emset-urlem
240type RemoteSetURLAddOptions struct {
241	// Indicates whether to get push URLs instead of fetch URLs.
242	Push bool
243	CommandOptions
244}
245
246// RemoteSetURLAdd appends an URL to the remote with given name of the
247// repository. Use RemoteSetURL to overwrite the URL(s) instead.
248func (r *Repository) RemoteSetURLAdd(ctx context.Context, name, newurl string, opts ...RemoteSetURLAddOptions) error {
249	var opt RemoteSetURLAddOptions
250	if len(opts) > 0 {
251		opt = opts[0]
252	}
253
254	args := []string{"remote", "set-url", "--add"}
255	if opt.Push {
256		args = append(args, "--push")
257	}
258	args = append(args, "--end-of-options", name, newurl)
259
260	_, err := exec(ctx, r.path, args, opt.Envs)
261	if err != nil && strings.Contains(err.Error(), "Will not delete all non-push URLs") {
262		return ErrNotDeleteNonPushURLs
263	}
264	return err
265}
266
267// RemoteSetURLDeleteOptions contains arguments for deleting an URL of a remote
268// of the repository.
269//
270// Docs: https://git-scm.com/docs/git-remote#Documentation/git-remote.txt-emset-urlem
271type RemoteSetURLDeleteOptions struct {
272	// Indicates whether to get push URLs instead of fetch URLs.
273	Push bool
274	CommandOptions
275}
276
277// RemoteSetURLDelete deletes all URLs matching regex of the remote with given
278// name of the repository.
279func (r *Repository) RemoteSetURLDelete(ctx context.Context, name, regex string, opts ...RemoteSetURLDeleteOptions) error {
280	var opt RemoteSetURLDeleteOptions
281	if len(opts) > 0 {
282		opt = opts[0]
283	}
284
285	args := []string{"remote", "set-url", "--delete"}
286	if opt.Push {
287		args = append(args, "--push")
288	}
289	args = append(args, "--end-of-options", name, regex)
290
291	_, err := exec(ctx, r.path, args, opt.Envs)
292	if err != nil && strings.Contains(err.Error(), "Will not delete all non-push URLs") {
293		return ErrNotDeleteNonPushURLs
294	}
295	return err
296}