@@ -44,6 +44,10 @@ func (c *Client) branchURL() string {
4444 return fmt .Sprintf ("%s/repos/%s/%s/branches/%s" , c .baseURL , c .owner , c .repo , c .branch )
4545}
4646
47+ func (c * Client ) refsURL () string {
48+ return fmt .Sprintf ("%s/repos/%s/%s/git/refs" , c .baseURL , c .owner , c .repo )
49+ }
50+
4751func (c * Client ) browseCommitsURL () string {
4852 return fmt .Sprintf ("https://github.com/%s/%s/commits/%s" , c .owner , c .repo , c .branch )
4953}
@@ -57,7 +61,8 @@ func (c *Client) graphqlURL() string {
5761}
5862
5963// GetHeadCommitHash returns the current head commit hash for the configured repository and branch
60- func (c * Client ) GetHeadCommitHash (ctx context.Context ) (string , error ) {
64+ // If the branch does not exist (404 return), we'll attempt to create it from commit branchFrom
65+ func (c * Client ) GetHeadCommitHash (ctx context.Context , branchFrom string ) (string , error ) {
6166 req , err := http .NewRequestWithContext (ctx , http .MethodGet , c .branchURL (), nil )
6267 if err != nil {
6368 return "" , fmt .Errorf ("prepare http request: %w" , err )
@@ -69,6 +74,14 @@ func (c *Client) GetHeadCommitHash(ctx context.Context) (string, error) {
6974 }
7075 defer resp .Body .Close ()
7176
77+ if resp .StatusCode == http .StatusNotFound {
78+ if branchFrom != "" {
79+ return c .createBranch (ctx , branchFrom )
80+ }
81+
82+ return "" , fmt .Errorf ("branch %q does not exist on the remote" , c .branch )
83+ }
84+
7285 if resp .StatusCode != http .StatusOK {
7386 return "" , fmt .Errorf ("get commit hash: http %d" , resp .StatusCode )
7487 }
@@ -86,6 +99,50 @@ func (c *Client) GetHeadCommitHash(ctx context.Context) (string, error) {
8699 return payload .Commit .Sha , nil
87100}
88101
102+ // createBranch attempts to create c.branch using branchFrom as the branch point
103+ func (c * Client ) createBranch (ctx context.Context , branchFrom string ) (string , error ) {
104+ var input bytes.Buffer
105+
106+ err := json .NewEncoder (& input ).Encode (map [string ]string {
107+ "ref" : fmt .Sprintf ("refs/heads/%s" , c .branch ),
108+ "sha" : branchFrom ,
109+ })
110+ if err != nil {
111+ return "" , err
112+ }
113+
114+ req , err := http .NewRequestWithContext (ctx , http .MethodPost , c .refsURL (), & input )
115+ if err != nil {
116+ return "" , fmt .Errorf ("prepare http request: %w" , err )
117+ }
118+
119+ resp , err := c .httpC .Do (req )
120+ if err != nil {
121+ return "" , fmt .Errorf ("create branch request: %w" , err )
122+ }
123+ defer resp .Body .Close ()
124+
125+ if resp .StatusCode == http .StatusUnprocessableEntity {
126+ return "" , fmt .Errorf ("create branch: http 422 (does the branch point exist?)" )
127+ }
128+
129+ if resp .StatusCode != http .StatusCreated {
130+ return "" , fmt .Errorf ("create branch: http %d" , resp .StatusCode )
131+ }
132+
133+ payload := struct {
134+ Commit struct {
135+ Sha string
136+ } `json:"object"`
137+ }{}
138+
139+ if err := json .NewDecoder (resp .Body ).Decode (& payload ); err != nil {
140+ return "" , fmt .Errorf ("decode create branch response: %w" , err )
141+ }
142+
143+ return payload .Commit .Sha , nil
144+ }
145+
89146// PushChanges takes a list of changes and a commit hash and produces commits using the GitHub GraphQL API.
90147// The commit hash is expected to be the current head of the remote branch, see [GetHeadCommitHash]
91148// for more.
0 commit comments