@@ -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,52 @@ 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+ log ("Creating branch from commit %s\n " , branchFrom )
105+
106+ var input bytes.Buffer
107+
108+ err := json .NewEncoder (& input ).Encode (map [string ]string {
109+ "ref" : fmt .Sprintf ("refs/heads/%s" , c .branch ),
110+ "sha" : branchFrom ,
111+ })
112+ if err != nil {
113+ return "" , err
114+ }
115+
116+ req , err := http .NewRequestWithContext (ctx , http .MethodPost , c .refsURL (), & input )
117+ if err != nil {
118+ return "" , fmt .Errorf ("prepare http request: %w" , err )
119+ }
120+
121+ resp , err := c .httpC .Do (req )
122+ if err != nil {
123+ return "" , fmt .Errorf ("create branch request: %w" , err )
124+ }
125+ defer resp .Body .Close ()
126+
127+ if resp .StatusCode == http .StatusUnprocessableEntity {
128+ return "" , fmt .Errorf ("create branch: http 422 (does the branch point exist?)" )
129+ }
130+
131+ if resp .StatusCode != http .StatusCreated {
132+ return "" , fmt .Errorf ("create branch: http %d" , resp .StatusCode )
133+ }
134+
135+ payload := struct {
136+ Commit struct {
137+ Sha string
138+ } `json:"object"`
139+ }{}
140+
141+ if err := json .NewDecoder (resp .Body ).Decode (& payload ); err != nil {
142+ return "" , fmt .Errorf ("decode create branch response: %w" , err )
143+ }
144+
145+ return payload .Commit .Sha , nil
146+ }
147+
89148// PushChanges takes a list of changes and a commit hash and produces commits using the GitHub GraphQL API.
90149// The commit hash is expected to be the current head of the remote branch, see [GetHeadCommitHash]
91150// for more.
0 commit comments