Skip to content

Commit 0c9eb52

Browse files
committed
cmd/gg: add pull --pattern flag
1 parent 1c56b51 commit 0c9eb52

File tree

4 files changed

+87
-38
lines changed

4 files changed

+87
-38
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ The format is based on [Keep a Changelog][], and this project adheres to
88

99
## [Unreleased][]
1010

11+
### Added
12+
13+
- `pull` now accepts a `--pattern` flag.
14+
1115
### Changed
1216

1317
- If multiple `--pattern` flags are given to `branch`,

cmd/gg/pull.go

Lines changed: 81 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"context"
1919
"fmt"
2020
"io"
21+
"regexp"
2122
"strings"
2223

2324
"gg-scm.io/pkg/git"
@@ -42,8 +43,11 @@ func pull(ctx context.Context, cc *cmdContext, args []string) error {
4243
If no revisions are specified, then all the remote's branches and tags
4344
will be fetched. If the source is a named remote, then its remote
4445
tracking branches will be pruned.`)
45-
remoteRefArgs := f.MultiString("r", "`ref`s to pull")
46-
forceTags := f.Bool("force-tags", false, "update any tags pulled")
46+
var input pullInput
47+
f.MultiStringVar(&input.remoteRefArgs, "r", "`ref`s to pull")
48+
f.RegexpVar(&input.remoteRefPattern, "p", "`regexp` of branch or tag names to pull (can be specified multiple times)")
49+
f.Alias("p", "pattern")
50+
f.BoolVar(&input.forceTags, "force-tags", false, "update any tags pulled")
4751
update := f.Bool("u", false, "update to new head if new descendants were pulled")
4852
if err := f.Parse(args); flag.IsHelp(err) {
4953
f.Help(cc.stdout)
@@ -58,35 +62,35 @@ func pull(ctx context.Context, cc *cmdContext, args []string) error {
5862
if err != nil {
5963
return err
6064
}
61-
remotes := cfg.ListRemotes()
65+
input.remotes = cfg.ListRemotes()
6266
headBranch := currentBranch(ctx, cc)
63-
repo := f.Arg(0)
64-
if repo == "" {
65-
repo = "origin"
66-
if _, ok := remotes[repo]; !ok {
67-
return fmt.Errorf("no source given and no remote named %q found", repo)
67+
input.repo = f.Arg(0)
68+
if input.repo == "" {
69+
input.repo = "origin"
70+
if _, ok := input.remotes[input.repo]; !ok {
71+
return fmt.Errorf("no source given and no remote named %q found", input.repo)
6872
}
6973
}
70-
allLocalRefs, err := cc.git.ListRefsVerbatim(ctx)
74+
input.localRefs, err = cc.git.ListRefsVerbatim(ctx)
7175
if err != nil {
7276
return err
7377
}
74-
allRemoteRefs, err := cc.git.ListRemoteRefs(ctx, repo)
78+
input.remoteRefs, err = cc.git.ListRemoteRefs(ctx, input.repo)
7579
if err != nil {
7680
return err
7781
}
7882

79-
remote := remotes[repo]
80-
gitArgs, ops, err := buildFetchArgs(repo, remotes, allLocalRefs, allRemoteRefs, *remoteRefArgs, *forceTags)
83+
gitArgs, ops, err := input.buildFetchArgs()
8184
if err != nil {
8285
return err
8386
}
87+
remote := input.remotes[input.repo]
8488
if remote == nil {
8589
// Delete anything under refs/ggpull/...
8690
// (Need to do this before fetching, but after validating that this
8791
// invocation will be reasonable.)
88-
localMuts := make(map[git.Ref]git.RefMutation, len(allLocalRefs))
89-
for ref := range allLocalRefs {
92+
localMuts := make(map[git.Ref]git.RefMutation, len(input.localRefs))
93+
for ref := range input.localRefs {
9094
if strings.HasPrefix(ref.String(), "refs/ggpull/") {
9195
localMuts[ref] = git.DeleteRef()
9296
}
@@ -109,7 +113,7 @@ func pull(ctx context.Context, cc *cmdContext, args []string) error {
109113
var target git.Ref
110114
if remote != nil {
111115
headRef := git.BranchRef(headBranch)
112-
for _, spec := range remotes[repo].Fetch {
116+
for _, spec := range remote.Fetch {
113117
target = spec.Map(headRef)
114118
if target != "" {
115119
break
@@ -125,6 +129,21 @@ func pull(ctx context.Context, cc *cmdContext, args []string) error {
125129
return nil
126130
}
127131

132+
type pullInput struct {
133+
remoteRefArgs []string
134+
remoteRefPattern *regexp.Regexp
135+
forceTags bool
136+
137+
// repo is the name or URL of the remote to fetch from.
138+
repo string
139+
// remotes is the configured set of remotes.
140+
remotes map[string]*git.Remote
141+
// localRefs is the set of refs in the local repository.
142+
localRefs map[git.Ref]git.Hash
143+
// remoteRefs is the set of refs in the repository being fetched.
144+
remoteRefs map[git.Ref]git.Hash
145+
}
146+
128147
type deferredFetchOps struct {
129148
remote *git.Remote
130149
localRefs map[git.Ref]git.Hash
@@ -136,91 +155,116 @@ type deferredFetchOps struct {
136155
// buildFetchArgs computes the set of branches and tags to fetch.
137156
// If buildFetchArgs returns an empty list of arguments,
138157
// then fetch should not be run.
139-
func buildFetchArgs(repo string, remotes map[string]*git.Remote, localRefs, remoteRefs map[git.Ref]git.Hash, remoteRefArgs []string, forceTags bool) (gitArgs []string, ops *deferredFetchOps, _ error) {
158+
func (input *pullInput) buildFetchArgs() (gitArgs []string, ops *deferredFetchOps, _ error) {
140159
ops = &deferredFetchOps{
141-
remote: remotes[repo],
142-
localRefs: localRefs,
143-
remoteRefs: remoteRefs,
160+
remote: input.remotes[input.repo],
161+
localRefs: input.localRefs,
162+
remoteRefs: input.remoteRefs,
144163
deletedRefs: make(map[git.Ref]git.Hash),
145164
}
146165
gitArgs = []string{"fetch"}
147166
var prevRemoteRefs map[git.Ref]git.Hash
148167
if ops.remote != nil {
149-
prevRemoteRefs = reverseFetchMap(ops.remote.Fetch, localRefs)
168+
prevRemoteRefs = reverseFetchMap(ops.remote.Fetch, input.localRefs)
150169
} else {
151170
gitArgs = append(gitArgs, "--refmap=+refs/heads/*:refs/ggpull/*")
152171
}
153172

154173
// Convert remoteRefArgs into a set of refs.
155174
var resolvedRefs []git.Ref
156-
if len(remoteRefArgs) == 0 {
175+
if len(input.remoteRefArgs) == 0 && input.remoteRefPattern == nil {
157176
// Empty means everything.
158-
for ref := range remoteRefs {
177+
for ref := range input.remoteRefs {
159178
if ref.IsBranch() || ref.IsTag() {
160179
resolvedRefs = append(resolvedRefs, ref)
161180
}
162181
}
163182
for ref := range prevRemoteRefs {
164-
if _, exists := remoteRefs[ref]; ref.IsBranch() && !exists {
183+
if _, exists := input.remoteRefs[ref]; ref.IsBranch() && !exists {
165184
resolvedRefs = append(resolvedRefs, ref)
166185
}
167186
}
168187
} else {
169188
// Add refs/heads/ or refs/tags/ as appropriate.
170-
for _, arg := range remoteRefArgs {
189+
for _, arg := range input.remoteRefArgs {
171190
if ref := git.Ref(arg); ref.IsBranch() || ref.IsTag() {
172191
resolvedRefs = append(resolvedRefs, ref)
173192
continue
174193
}
175194
branchRef := git.BranchRef(arg)
176-
_, hasBranch := remoteRefs[branchRef]
195+
_, hasBranch := input.remoteRefs[branchRef]
177196
_, hasPrevBranch := prevRemoteRefs[branchRef]
178197
if hasBranch || hasPrevBranch {
179198
resolvedRefs = append(resolvedRefs, branchRef)
180199
continue
181200
}
182201
tagRef := git.TagRef(arg)
183-
if _, hasTag := remoteRefs[tagRef]; hasTag {
202+
if _, hasTag := input.remoteRefs[tagRef]; hasTag {
184203
resolvedRefs = append(resolvedRefs, tagRef)
185204
continue
186205
}
187-
return nil, nil, fmt.Errorf("can't find ref %q on remote %q", arg, repo)
206+
return nil, nil, fmt.Errorf("can't find ref %q on remote %q", arg, input.repo)
207+
}
208+
209+
if input.remoteRefPattern != nil {
210+
for ref := range input.remoteRefs {
211+
s := ref.Branch()
212+
if s == "" {
213+
s = ref.Tag()
214+
if s == "" {
215+
continue
216+
}
217+
}
218+
if input.remoteRefPattern.MatchString(s) {
219+
resolvedRefs = append(resolvedRefs, ref)
220+
}
221+
}
222+
for ref := range prevRemoteRefs {
223+
branch := ref.Branch()
224+
if branch == "" {
225+
continue
226+
}
227+
_, exists := input.remoteRefs[ref]
228+
if !exists && input.remoteRefPattern.MatchString(ref.String()) {
229+
resolvedRefs = append(resolvedRefs, ref)
230+
}
231+
}
188232
}
189233
}
190234

191235
// Build fetch arguments and validate ref selection.
192-
gitArgs = append(gitArgs, "--", repo)
236+
gitArgs = append(gitArgs, "--", input.repo)
193237
zeroFetchArgsLen := len(gitArgs)
194238
for _, ref := range resolvedRefs {
195239
switch {
196240
case ref.IsBranch():
197-
_, hasBranch := remoteRefs[ref]
241+
_, hasBranch := input.remoteRefs[ref]
198242
_, hasPrevBranch := prevRemoteRefs[ref]
199243
switch {
200244
case hasBranch:
201245
gitArgs = append(gitArgs, ref.String()+":")
202246
ops.branches = append(ops.branches, ref)
203247
case hasPrevBranch:
204248
trackingRef := ops.remote.MapFetch(ref)
205-
ops.deletedRefs[trackingRef] = localRefs[trackingRef]
206-
if isRefOrphaned(remotes, localRefs, repo, ref) {
207-
ops.deletedRefs[ref] = localRefs[ref]
249+
ops.deletedRefs[trackingRef] = input.localRefs[trackingRef]
250+
if isRefOrphaned(input.remotes, input.localRefs, input.repo, ref) {
251+
ops.deletedRefs[ref] = input.localRefs[ref]
208252
}
209253
default:
210-
return nil, nil, fmt.Errorf("can't find ref %q on remote %q", ref, repo)
254+
return nil, nil, fmt.Errorf("can't find ref %q on remote %q", ref, input.repo)
211255
}
212256
case ref.IsTag():
213-
localHash, hasLocalTag := localRefs[ref]
214-
remoteHash, hasRemoteTag := remoteRefs[ref]
257+
localHash, hasLocalTag := input.localRefs[ref]
258+
remoteHash, hasRemoteTag := input.remoteRefs[ref]
215259
refspec := ref.String() + ":" + ref.String()
216260
switch {
217261
case !hasRemoteTag:
218262
return nil, nil, fmt.Errorf("tag %q does not exist on remote", ref.Tag())
219263
case !hasLocalTag:
220264
gitArgs = append(gitArgs, refspec)
221-
case localHash != remoteHash && forceTags:
265+
case localHash != remoteHash && input.forceTags:
222266
gitArgs = append(gitArgs, "+"+refspec)
223-
case localHash != remoteHash && !forceTags:
267+
case localHash != remoteHash && !input.forceTags:
224268
return nil, nil, fmt.Errorf("tag %q is %v on remote, does not match %v locally", ref.Tag(), remoteHash, localHash)
225269
}
226270
default:

misc/_gg.zsh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ case "${words[2]}" in
206206
_arguments -S : \
207207
':command:' \
208208
'-r=[remote reference intended to be pulled]:remote ref:branches' \
209+
'*'{-p,-pattern}'=[regexp of branch or tag names to pull]' \
209210
'-force-tags[update any tags pulled]' \
210211
'-u[update to new head if new descendants were pulled]' \
211212
':source:remotes'

misc/gg.bash

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ _gg_complete() {
133133
return 0
134134
;;
135135
pull)
136-
COMPREPLY=( $(compgen -W '-force-tags --force-tags -r -u' -- "$curr_word") )
136+
COMPREPLY=( $(compgen -W '-force-tags --force-tags -p -pattern --pattern -r -u' -- "$curr_word") )
137137
return 0
138138
;;
139139
push)

0 commit comments

Comments
 (0)