11package gitutil
22
33import (
4- "bytes"
54 "context"
65 "net/url"
7- "os"
8- "os/exec"
9- "path/filepath"
106 "strings"
117
128 "github.com/docker/buildx/util/osutil"
9+ bkgitutil "github.com/moby/buildkit/util/gitutil"
1310 "github.com/pkg/errors"
1411)
1512
16- // Git represents an active git object
13+ // Git represents an active git object.
1714type Git struct {
18- ctx context.Context
19- wd string
20- gitpath string
15+ ctx context.Context
16+ cli * bkgitutil.GitCLI
2117}
2218
2319// Option provides a variadic option for configuring the git client.
24- type Option func (b * Git )
20+ type Option func (* Git )
2521
2622// WithContext sets context.
2723func WithContext (ctx context.Context ) Option {
28- return func (b * Git ) {
29- b .ctx = ctx
24+ return func (g * Git ) {
25+ g .ctx = ctx
3026 }
3127}
3228
3329// WithWorkingDir sets working directory.
3430func WithWorkingDir (wd string ) Option {
35- return func (b * Git ) {
36- b .wd = wd
31+ return func (g * Git ) {
32+ if g .cli == nil {
33+ g .cli = bkgitutil .NewGitCLI ()
34+ }
35+ g .cli = g .cli .New (bkgitutil .WithDir (wd ))
3736 }
3837}
3938
40- // New initializes a new git client
39+ // New initializes a new git client.
4140func New (opts ... Option ) (* Git , error ) {
42- var err error
43- c := & Git {
41+ g := & Git {
4442 ctx : context .Background (),
43+ cli : bkgitutil .NewGitCLI (),
4544 }
4645
4746 for _ , opt := range opts {
48- opt (c )
47+ opt (g )
4948 }
5049
51- c . gitpath , err = gitPath (c . wd )
50+ gitpath , err : = gitPath (g . cli . Dir () )
5251 if err != nil {
5352 return nil , err
5453 }
5554
56- return c , nil
55+ g .cli = g .cli .New (
56+ bkgitutil .WithGitBinary (gitpath ),
57+ bkgitutil .WithArgs ("-c" , "log.showSignature=false" ),
58+ )
59+ return g , nil
5760}
5861
59- func (c * Git ) IsInsideWorkTree () bool {
60- out , err := c .Run ("rev-parse" , "--is-inside-work-tree" )
62+ func (g * Git ) IsInsideWorkTree () bool {
63+ out , err := g .Run ("rev-parse" , "--is-inside-work-tree" )
6164 return out == "true" && err == nil
6265}
6366
64- func (c * Git ) IsDirty () bool {
65- out , err := c .Run ("status" , "--porcelain" , "--ignored" )
67+ func (g * Git ) IsDirty () bool {
68+ out , err := g .Run ("status" , "--porcelain" , "--ignored" )
6669 return strings .TrimSpace (out ) != "" || err != nil
6770}
6871
69- func (c * Git ) RootDir () (string , error ) {
70- root , err := c . Run ( "rev-parse" , "--show-toplevel" )
72+ func (g * Git ) RootDir () (string , error ) {
73+ root , err := g . cli . WorkTree ( g . ctx )
7174 if err != nil {
7275 return "" , err
7376 }
7477 return osutil .SanitizePath (root ), nil
7578}
7679
77- func (c * Git ) GitDir () (string , error ) {
78- dir , err := c . RootDir ( )
80+ func (g * Git ) GitDir () (string , error ) {
81+ dir , err := g . cli . GitDir ( g . ctx )
7982 if err != nil {
8083 return "" , err
8184 }
82- return filepath . Join (dir , ".git" ), nil
85+ return osutil . SanitizePath (dir ), nil
8386}
8487
85- func (c * Git ) RemoteURL () (string , error ) {
86- // Try default remote based on remote tracking branch
87- if remote , err := c .currentRemote (); err == nil && remote != "" {
88- if ru , err := c . clean ( c . run ( "remote" , "get-url" , remote ) ); err == nil && ru != "" {
88+ func (g * Git ) RemoteURL () (string , error ) {
89+ // Try default remote based on remote tracking branch.
90+ if remote , err := g .currentRemote (); err == nil && remote != "" {
91+ if ru , err := g . Run ( "remote" , "get-url" , remote ); err == nil && ru != "" {
8992 return stripCredentials (ru ), nil
9093 }
9194 }
92- // Next try to get the remote URL from the origin remote first
93- if ru , err := c . clean ( c . run ( "remote" , "get-url" , "origin" ) ); err == nil && ru != "" {
95+ // Next try to get the remote URL from the origin remote first.
96+ if ru , err := g . Run ( "remote" , "get-url" , "origin" ); err == nil && ru != "" {
9497 return stripCredentials (ru ), nil
9598 }
96- // If that fails, try to get the remote URL from the upstream remote
97- if ru , err := c . clean ( c . run ( "remote" , "get-url" , "upstream" ) ); err == nil && ru != "" {
99+ // If that fails, try to get the remote URL from the upstream remote.
100+ if ru , err := g . Run ( "remote" , "get-url" , "upstream" ); err == nil && ru != "" {
98101 return stripCredentials (ru ), nil
99102 }
100103 return "" , errors .New ("no remote URL found for either origin or upstream" )
101104}
102105
103- func (c * Git ) FullCommit () (string , error ) {
104- return c . clean ( c . run ( "show" , "--format=%H" , "HEAD" , "--quiet" , "--" ) )
106+ func (g * Git ) FullCommit () (string , error ) {
107+ return g . Run ( "show" , "--format=%H" , "HEAD" , "--quiet" , "--" )
105108}
106109
107- func (c * Git ) ShortCommit () (string , error ) {
108- return c . clean ( c . run ( "show" , "--format=%h" , "HEAD" , "--quiet" , "--" ) )
110+ func (g * Git ) ShortCommit () (string , error ) {
111+ return g . Run ( "show" , "--format=%h" , "HEAD" , "--quiet" , "--" )
109112}
110113
111- func (c * Git ) Tag () (string , error ) {
114+ func (g * Git ) Tag () (string , error ) {
112115 var tag string
113116 var err error
114117 for _ , fn := range []func () (string , error ){
115118 func () (string , error ) {
116- return c . clean ( c . run ( "tag" , "--points-at" , "HEAD" , "--sort" , "-version:creatordate" ) )
119+ return g . Run ( "tag" , "--points-at" , "HEAD" , "--sort" , "-version:creatordate" )
117120 },
118121 func () (string , error ) {
119- return c . clean ( c . run ( "describe" , "--tags" , "--abbrev=0" ) )
122+ return g . Run ( "describe" , "--tags" , "--abbrev=0" )
120123 },
121124 } {
122125 tag , err = fn ()
@@ -127,53 +130,34 @@ func (c *Git) Tag() (string, error) {
127130 return tag , err
128131}
129132
130- func (c * Git ) Run (args ... string ) (string , error ) {
131- return c .clean (c .run (args ... ))
132- }
133-
134- func (c * Git ) run (args ... string ) (string , error ) {
135- var extraArgs = []string {
136- "-c" , "log.showSignature=false" ,
137- }
138-
139- args = append (extraArgs , args ... )
140- cmd := exec .CommandContext (c .ctx , c .gitpath , args ... )
141- if c .wd != "" {
142- cmd .Dir = c .wd
143- }
144-
145- // Override the locale to ensure consistent output
146- cmd .Env = append (os .Environ (), "LC_ALL=C" )
147-
148- stdout := bytes.Buffer {}
149- stderr := bytes.Buffer {}
150- cmd .Stdout = & stdout
151- cmd .Stderr = & stderr
152-
153- if err := cmd .Run (); err != nil {
154- return "" , errors .New (stderr .String ())
155- }
156- return stdout .String (), nil
133+ func (g * Git ) Run (args ... string ) (string , error ) {
134+ return g .clean (g .cli .Run (g .ctx , args ... ))
157135}
158136
159- func (c * Git ) clean (out string , err error ) (string , error ) {
160- out = strings .ReplaceAll (strings .Split (out , "\n " )[0 ], "'" , "" )
137+ func (g * Git ) clean (dt [] byte , err error ) (string , error ) {
138+ out : = strings .ReplaceAll (strings .Split (string ( dt ) , "\n " )[0 ], "'" , "" )
161139 if err != nil {
162- err = errors .New (strings .TrimSuffix (err .Error (), "\n " ))
140+ msg := strings .TrimSuffix (err .Error (), "\n " )
141+ if stderr , ok := strings .CutPrefix (msg , "git stderr:\n " ); ok {
142+ if idx := strings .LastIndex (stderr , ": exit status " ); idx != - 1 {
143+ stderr = stderr [:idx ]
144+ }
145+ msg = strings .TrimSuffix (stderr , "\n " )
146+ }
147+ err = errors .New (msg )
163148 }
164149 return out , err
165150}
166151
167- func (c * Git ) currentRemote () (string , error ) {
168- symref , err := c .Run ("symbolic-ref" , "-q" , "HEAD" )
152+ func (g * Git ) currentRemote () (string , error ) {
153+ symref , err := g .Run ("symbolic-ref" , "-q" , "HEAD" )
169154 if err != nil {
170155 return "" , err
171156 }
172157 if symref == "" {
173158 return "" , nil
174159 }
175- // git for-each-ref --format='%(upstream:remotename)'
176- remote , err := c .Run ("for-each-ref" , "--format=%(upstream:remotename)" , symref )
160+ remote , err := g .Run ("for-each-ref" , "--format=%(upstream:remotename)" , symref )
177161 if err != nil {
178162 return "" , err
179163 }
0 commit comments