at master 160 lines 3.7 kB view raw
1package git 2 3import ( 4 "fmt" 5 "slices" 6 "strconv" 7 "strings" 8 "time" 9 10 "github.com/go-git/go-git/v5/plumbing" 11 "github.com/go-git/go-git/v5/plumbing/object" 12 "tangled.org/core/types" 13) 14 15type BranchesOptions struct { 16 Limit int 17 Offset int 18} 19 20func (g *GitRepo) Branches(opts *BranchesOptions) ([]types.Branch, error) { 21 if opts == nil { 22 opts = &BranchesOptions{} 23 } 24 25 fields := []string{ 26 "refname:short", 27 "objectname", 28 "authorname", 29 "authoremail", 30 "authordate:unix", 31 "committername", 32 "committeremail", 33 "committerdate:unix", 34 "tree", 35 "parent", 36 "contents", 37 } 38 39 var outFormat strings.Builder 40 outFormat.WriteString("--format=") 41 for i, f := range fields { 42 if i != 0 { 43 outFormat.WriteString(fieldSeparator) 44 } 45 fmt.Fprintf(&outFormat, "%%(%s)", f) 46 } 47 outFormat.WriteString("") 48 outFormat.WriteString(recordSeparator) 49 50 args := []string{outFormat.String(), "--sort=-creatordate"} 51 52 // only add the count if the limit is a non-zero value, 53 // if it is zero, get as many tags as we can 54 if opts.Limit > 0 { 55 args = append(args, fmt.Sprintf("--count=%d", opts.Offset+opts.Limit)) 56 } 57 58 args = append(args, "refs/heads") 59 60 output, err := g.forEachRef(args...) 61 if err != nil { 62 return nil, fmt.Errorf("failed to get branches: %w", err) 63 } 64 65 records := strings.Split(strings.TrimSpace(string(output)), recordSeparator) 66 if len(records) == 1 && records[0] == "" { 67 return nil, nil 68 } 69 70 startIdx := opts.Offset 71 if startIdx >= len(records) { 72 return nil, nil 73 } 74 75 endIdx := len(records) 76 if opts.Limit > 0 { 77 endIdx = min(startIdx+opts.Limit, len(records)) 78 } 79 80 records = records[startIdx:endIdx] 81 branches := make([]types.Branch, 0, len(records)) 82 83 // ignore errors here 84 defaultBranch, _ := g.FindMainBranch() 85 86 for _, line := range records { 87 parts := strings.SplitN(strings.TrimSpace(line), fieldSeparator, len(fields)) 88 if len(parts) < 6 { 89 continue 90 } 91 92 branchName := parts[0] 93 commitHash := plumbing.NewHash(parts[1]) 94 authorName := parts[2] 95 authorEmail := strings.TrimSuffix(strings.TrimPrefix(parts[3], "<"), ">") 96 authorDate := parts[4] 97 committerName := parts[5] 98 committerEmail := strings.TrimSuffix(strings.TrimPrefix(parts[6], "<"), ">") 99 committerDate := parts[7] 100 treeHash := plumbing.NewHash(parts[8]) 101 parentHash := plumbing.NewHash(parts[9]) 102 message := parts[10] 103 104 // parse creation time 105 var authoredAt, committedAt time.Time 106 if unix, err := strconv.ParseInt(authorDate, 10, 64); err == nil { 107 authoredAt = time.Unix(unix, 0) 108 } 109 if unix, err := strconv.ParseInt(committerDate, 10, 64); err == nil { 110 committedAt = time.Unix(unix, 0) 111 } 112 113 branch := types.Branch{ 114 IsDefault: branchName == defaultBranch, 115 Reference: types.Reference{ 116 Name: branchName, 117 Hash: commitHash.String(), 118 }, 119 Commit: &object.Commit{ 120 Hash: commitHash, 121 Author: object.Signature{ 122 Name: authorName, 123 Email: authorEmail, 124 When: authoredAt, 125 }, 126 Committer: object.Signature{ 127 Name: committerName, 128 Email: committerEmail, 129 When: committedAt, 130 }, 131 TreeHash: treeHash, 132 ParentHashes: []plumbing.Hash{parentHash}, 133 Message: message, 134 }, 135 } 136 137 branches = append(branches, branch) 138 } 139 140 slices.Reverse(branches) 141 return branches, nil 142} 143 144func (g *GitRepo) Branch(name string) (*plumbing.Reference, error) { 145 ref, err := g.r.Reference(plumbing.NewBranchReferenceName(name), false) 146 if err != nil { 147 return nil, fmt.Errorf("branch: %w", err) 148 } 149 150 if !ref.Name().IsBranch() { 151 return nil, fmt.Errorf("branch: %s is not a branch", ref.Name()) 152 } 153 154 return ref, nil 155} 156 157func (g *GitRepo) DeleteBranch(branch string) error { 158 ref := plumbing.NewBranchReferenceName(branch) 159 return g.r.Storer.RemoveReference(ref) 160}