nebius/SWE-rebench-V2-PRs huggingface dataset:
Repository: hetznercloud/hcloud-go
Pull request #304 (2023-08-15 12:28:41)

fix: include running action progress in WatchOverallProgress
Include the running action progress in the actions progress calculation.

The function now considers a failed action as completed, and is included in the progress. Not counting the failed action as completed might send some weird progress values e.g. [0%, 75%, (failed action occurs here) 50%], while if we count them, we should have [0%, 75%, 100%].



Related to https://github.com/hetznercloud/cli/issues/528


Patch:
diff --git a/hcloud/action.go b/hcloud/action.go
index c8e06f2..1375e0c 100644
--- a/hcloud/action.go
+++ b/hcloud/action.go
@@ -189,13 +189,14 @@ func (c *ActionClient) WatchOverallProgress(ctx context.Context, actions []*Acti
 		defer close(errCh)
 		defer close(progressCh)
 
-		successIDs := make([]int64, 0, len(actions))
+		completedIDs := make([]int64, 0, len(actions))
 		watchIDs := make(map[int64]struct{}, len(actions))
 		for _, action := range actions {
 			watchIDs[action.ID] = struct{}{}
 		}
 
 		retries := 0
+		previousProgress := 0
 
 		for {
 			select {
@@ -224,20 +225,27 @@ func (c *ActionClient) WatchOverallProgress(ctx context.Context, actions []*Acti
 				return
 			}
 
+			progress := 0
 			for _, a := range as {
 				switch a.Status {
 				case ActionStatusRunning:
-					continue
+					progress += a.Progress
 				case ActionStatusSuccess:
 					delete(watchIDs, a.ID)
-					successIDs = append(successIDs, a.ID)
-					sendProgress(progressCh, int(float64(len(actions)-len(successIDs))/float64(len(actions))*100))
+					completedIDs = append(completedIDs, a.ID)
 				case ActionStatusError:
 					delete(watchIDs, a.ID)
+					completedIDs = append(completedIDs, a.ID)
 					errCh <- fmt.Errorf("action %d failed: %w", a.ID, a.Error())
 				}
 			}
 
+			progress += (len(completedIDs) * 100)
+			if progress != 0 && progress != previousProgress {
+				sendProgress(progressCh, int(progress/len(actions)))
+				previousProgress = progress
+			}
+
 			if len(watchIDs) == 0 {
 				return
 			}


Test patch:
diff --git a/hcloud/action_test.go b/hcloud/action_test.go
index b7c42e9..b813bb7 100644
--- a/hcloud/action_test.go
+++ b/hcloud/action_test.go
@@ -281,7 +281,7 @@ func TestActionClientWatchOverallProgress(t *testing.T) {
 		t.Fatalf("expected hcloud.Error, but got: %#v", err)
 	}
 
-	expectedProgressUpdates := []int{50}
+	expectedProgressUpdates := []int{50, 100}
 	if !reflect.DeepEqual(progressUpdates, expectedProgressUpdates) {
 		t.Fatalf("expected progresses %v but received %v", expectedProgressUpdates, progressUpdates)
 	}
Repository: hetznercloud/hcloud-go
Pull request #306 (2023-08-17 10:45:05)

[Backport release-1.x] fix: send more precise progress values (#304)
Backport 867aa632521ad3acfb04beb52b6307330740fc68 from #304.


Patch:
diff --git a/hcloud/action.go b/hcloud/action.go
index 3131266..2f4d01e 100644
--- a/hcloud/action.go
+++ b/hcloud/action.go
@@ -189,13 +189,15 @@ func (c *ActionClient) WatchOverallProgress(ctx context.Context, actions []*Acti
 		defer close(errCh)
 		defer close(progressCh)
 
-		successIDs := make([]int, 0, len(actions))
+		completedIDs := make([]int, 0, len(actions))
 		watchIDs := make(map[int]struct{}, len(actions))
+
 		for _, action := range actions {
 			watchIDs[action.ID] = struct{}{}
 		}
 
 		retries := 0
+		previousProgress := 0
 
 		for {
 			select {
@@ -224,20 +226,27 @@ func (c *ActionClient) WatchOverallProgress(ctx context.Context, actions []*Acti
 				return
 			}
 
+			progress := 0
 			for _, a := range as {
 				switch a.Status {
 				case ActionStatusRunning:
-					continue
+					progress += a.Progress
 				case ActionStatusSuccess:
 					delete(watchIDs, a.ID)
-					successIDs = append(successIDs, a.ID)
-					sendProgress(progressCh, int(float64(len(actions)-len(successIDs))/float64(len(actions))*100))
+					completedIDs = append(completedIDs, a.ID)
 				case ActionStatusError:
 					delete(watchIDs, a.ID)
+					completedIDs = append(completedIDs, a.ID)
 					errCh <- fmt.Errorf("action %d failed: %w", a.ID, a.Error())
 				}
 			}
 
+			progress += (len(completedIDs) * 100)
+			if progress != 0 && progress != previousProgress {
+				sendProgress(progressCh, int(progress/len(actions)))
+				previousProgress = progress
+			}
+
 			if len(watchIDs) == 0 {
 				return
 			}


Test patch:
diff --git a/hcloud/action_test.go b/hcloud/action_test.go
index 0f69b85..70b0f3f 100644
--- a/hcloud/action_test.go
+++ b/hcloud/action_test.go
@@ -281,7 +281,7 @@ func TestActionClientWatchOverallProgress(t *testing.T) {
 		t.Fatalf("expected hcloud.Error, but got: %#v", err)
 	}
 
-	expectedProgressUpdates := []int{50}
+	expectedProgressUpdates := []int{50, 100}
 	if !reflect.DeepEqual(progressUpdates, expectedProgressUpdates) {
 		t.Fatalf("expected progresses %v but received %v", expectedProgressUpdates, progressUpdates)
 	}
Repository: hetznercloud/hcloud-go
Pull request #320 (2023-09-19 13:12:05)

feat(error): include http response in api errors
This allows us to show more useful information in debug logs in downstream projects. 


Patch:
diff --git a/hcloud/client.go b/hcloud/client.go
index f609ccf..5be4c7b 100644
--- a/hcloud/client.go
+++ b/hcloud/client.go
@@ -286,8 +286,8 @@ func (c *Client) Do(r *http.Request, v interface{}) (*Response, error) {
 			return response, fmt.Errorf("hcloud: error reading response meta data: %s", err)
 		}
 
-		if resp.StatusCode >= 400 && resp.StatusCode <= 599 {
-			err = errorFromResponse(resp, body)
+		if response.StatusCode >= 400 && response.StatusCode <= 599 {
+			err = errorFromResponse(response, body)
 			if err == nil {
 				err = fmt.Errorf("hcloud: server responded with status code %d", resp.StatusCode)
 			} else if IsError(err, ErrorCodeConflict) {
@@ -359,7 +359,7 @@ func dumpRequest(r *http.Request) ([]byte, error) {
 	return dumpReq, nil
 }
 
-func errorFromResponse(resp *http.Response, body []byte) error {
+func errorFromResponse(resp *Response, body []byte) error {
 	if !strings.HasPrefix(resp.Header.Get("Content-Type"), "application/json") {
 		return nil
 	}
@@ -371,7 +371,10 @@ func errorFromResponse(resp *http.Response, body []byte) error {
 	if respBody.Error.Code == "" && respBody.Error.Message == "" {
 		return nil
 	}
-	return ErrorFromSchema(respBody.Error)
+
+	hcErr := ErrorFromSchema(respBody.Error)
+	hcErr.response = resp
+	return hcErr
 }
 
 // Response represents a response from the API. It embeds http.Response.
diff --git a/hcloud/error.go b/hcloud/error.go
index ac689d1..653043e 100644
--- a/hcloud/error.go
+++ b/hcloud/error.go
@@ -94,12 +94,19 @@ type Error struct {
 	Code    ErrorCode
 	Message string
 	Details interface{}
+
+	response *Response
 }
 
 func (e Error) Error() string {
 	return fmt.Sprintf("%s (%s)", e.Message, e.Code)
 }
 
+// Response returns the [Response] that contained the error if available.
+func (e Error) Response() *Response {
+	return e.response
+}
+
 // ErrorDetailsInvalidInput contains the details of an 'invalid_input' error.
 type ErrorDetailsInvalidInput struct {
 	Fields []ErrorDetailsInvalidInputField


Test patch:
diff --git a/hcloud/client_test.go b/hcloud/client_test.go
index 36164cb..49d2023 100644
--- a/hcloud/client_test.go
+++ b/hcloud/client_test.go
@@ -85,6 +85,9 @@ func TestClientError(t *testing.T) {
 	if apiError.Message != "An error occurred" {
 		t.Errorf("unexpected error message: %q", apiError.Message)
 	}
+	if apiError.Response().StatusCode != http.StatusUnprocessableEntity {
+		t.Errorf("unexpected http status code: %q", apiError.Response().StatusCode)
+	}
 }
 
 func TestClientInvalidToken(t *testing.T) {
Repository: hetznercloud/hcloud-go
Pull request #331 (2023-10-12 13:13:51)

feat(iso): support deprecation info API
See these changelog entries for the announcement:

- https://docs.hetzner.cloud/changelog#2023-10-12-deprecation-info-for-isos
- https://docs.hetzner.cloud/changelog#2023-10-12-field-deprecated-on-isos-is-now-deprecated

The removed function `func (iso *ISO) IsDeprecated() bool` will still be available, but through the `DeprecatableResource` embbeded struct. User code will still work as before, but use the new data source instead.


Patch:
diff --git a/hcloud/iso.go b/hcloud/iso.go
index c893846..08f7072 100644
--- a/hcloud/iso.go
+++ b/hcloud/iso.go
@@ -17,12 +17,9 @@ type ISO struct {
 	Description  string
 	Type         ISOType
 	Architecture *Architecture
-	Deprecated   time.Time
-}
-
-// IsDeprecated returns true if the ISO is deprecated.
-func (iso *ISO) IsDeprecated() bool {
-	return !iso.Deprecated.IsZero()
+	// Deprecated: Use [ISO.Deprecation] instead.
+	Deprecated time.Time
+	DeprecatableResource
 }
 
 // ISOType specifies the type of an ISO image.
diff --git a/hcloud/schema.go b/hcloud/schema.go
index 3855c69..b7f046f 100644
--- a/hcloud/schema.go
+++ b/hcloud/schema.go
@@ -124,6 +124,9 @@ func ISOFromSchema(s schema.ISO) *ISO {
 		Description: s.Description,
 		Type:        ISOType(s.Type),
 		Deprecated:  s.Deprecated,
+		DeprecatableResource: DeprecatableResource{
+			DeprecationFromSchema(s.Deprecation),
+		},
 	}
 	if s.Architecture != nil {
 		iso.Architecture = Ptr(Architecture(*s.Architecture))
diff --git a/hcloud/schema/iso.go b/hcloud/schema/iso.go
index 4f89dd0..0fcb1a6 100644
--- a/hcloud/schema/iso.go
+++ b/hcloud/schema/iso.go
@@ -10,6 +10,7 @@ type ISO struct {
 	Type         string    `json:"type"`
 	Architecture *string   `json:"architecture"`
 	Deprecated   time.Time `json:"deprecated"`
+	DeprecatableResource
 }
 
 // ISOGetResponse defines the schema of the response when retrieving a single ISO.


Test patch:
diff --git a/hcloud/iso_test.go b/hcloud/iso_test.go
index 7e507f7..11fe908 100644
--- a/hcloud/iso_test.go
+++ b/hcloud/iso_test.go
@@ -20,7 +20,7 @@ func TestISOIsDeprecated(t *testing.T) {
 
 	t.Run("deprecated", func(t *testing.T) {
 		iso := &ISO{
-			Deprecated: time.Now(),
+			DeprecatableResource: DeprecatableResource{Deprecation: &DeprecationInfo{Announced: time.Now()}},
 		}
 		if !iso.IsDeprecated() {
 			t.Errorf("unexpected value for IsDeprecated: %v", iso.IsDeprecated())
Repository: hetznercloud/hcloud-go
Pull request #333 (2023-10-12 13:21:34)

[Backport release-1.x] feat(iso): support deprecation info API
Backport b3a36214b21ab1c5add94c2b1896a995342757b6 from #331.


Patch:
diff --git a/hcloud/iso.go b/hcloud/iso.go
index a858620..b4a4855 100644
--- a/hcloud/iso.go
+++ b/hcloud/iso.go
@@ -17,12 +17,9 @@ type ISO struct {
 	Description  string
 	Type         ISOType
 	Architecture *Architecture
-	Deprecated   time.Time
-}
-
-// IsDeprecated returns true if the ISO is deprecated.
-func (iso *ISO) IsDeprecated() bool {
-	return !iso.Deprecated.IsZero()
+	// Deprecated: Use [ISO.Deprecation] instead.
+	Deprecated time.Time
+	DeprecatableResource
 }
 
 // ISOType specifies the type of an ISO image.
diff --git a/hcloud/schema.go b/hcloud/schema.go
index f4a9366..09c0386 100644
--- a/hcloud/schema.go
+++ b/hcloud/schema.go
@@ -124,6 +124,9 @@ func ISOFromSchema(s schema.ISO) *ISO {
 		Description: s.Description,
 		Type:        ISOType(s.Type),
 		Deprecated:  s.Deprecated,
+		DeprecatableResource: DeprecatableResource{
+			DeprecationFromSchema(s.Deprecation),
+		},
 	}
 	if s.Architecture != nil {
 		iso.Architecture = Ptr(Architecture(*s.Architecture))
diff --git a/hcloud/schema/iso.go b/hcloud/schema/iso.go
index dfcc4e3..e1f38c1 100644
--- a/hcloud/schema/iso.go
+++ b/hcloud/schema/iso.go
@@ -10,6 +10,7 @@ type ISO struct {
 	Type         string    `json:"type"`
 	Architecture *string   `json:"architecture"`
 	Deprecated   time.Time `json:"deprecated"`
+	DeprecatableResource
 }
 
 // ISOGetResponse defines the schema of the response when retrieving a single ISO.


Test patch:
diff --git a/hcloud/iso_test.go b/hcloud/iso_test.go
index 1d010a7..76e279f 100644
--- a/hcloud/iso_test.go
+++ b/hcloud/iso_test.go
@@ -20,7 +20,7 @@ func TestISOIsDeprecated(t *testing.T) {
 
 	t.Run("deprecated", func(t *testing.T) {
 		iso := &ISO{
-			Deprecated: time.Now(),
+			DeprecatableResource: DeprecatableResource{Deprecation: &DeprecationInfo{Announced: time.Now()}},
 		}
 		if !iso.IsDeprecated() {
 			t.Errorf("unexpected value for IsDeprecated: %v", iso.IsDeprecated())
Repository: hetznercloud/hcloud-go
Pull request #335 (2023-10-20 09:14:39)

[Backport release-1.x] feat(error): include http response in api errors
Backport 95582395dfb1039f4ce4f10a1ac9c068db93a867 from #320.


Patch:
diff --git a/hcloud/client.go b/hcloud/client.go
index 7853580..570066a 100644
--- a/hcloud/client.go
+++ b/hcloud/client.go
@@ -286,8 +286,8 @@ func (c *Client) Do(r *http.Request, v interface{}) (*Response, error) {
 			return response, fmt.Errorf("hcloud: error reading response meta data: %s", err)
 		}
 
-		if resp.StatusCode >= 400 && resp.StatusCode <= 599 {
-			err = errorFromResponse(resp, body)
+		if response.StatusCode >= 400 && response.StatusCode <= 599 {
+			err = errorFromResponse(response, body)
 			if err == nil {
 				err = fmt.Errorf("hcloud: server responded with status code %d", resp.StatusCode)
 			} else if IsError(err, ErrorCodeConflict) {
@@ -359,7 +359,7 @@ func dumpRequest(r *http.Request) ([]byte, error) {
 	return dumpReq, nil
 }
 
-func errorFromResponse(resp *http.Response, body []byte) error {
+func errorFromResponse(resp *Response, body []byte) error {
 	if !strings.HasPrefix(resp.Header.Get("Content-Type"), "application/json") {
 		return nil
 	}
@@ -371,7 +371,10 @@ func errorFromResponse(resp *http.Response, body []byte) error {
 	if respBody.Error.Code == "" && respBody.Error.Message == "" {
 		return nil
 	}
-	return ErrorFromSchema(respBody.Error)
+
+	hcErr := ErrorFromSchema(respBody.Error)
+	hcErr.response = resp
+	return hcErr
 }
 
 // Response represents a response from the API. It embeds http.Response.
diff --git a/hcloud/error.go b/hcloud/error.go
index ac689d1..653043e 100644
--- a/hcloud/error.go
+++ b/hcloud/error.go
@@ -94,12 +94,19 @@ type Error struct {
 	Code    ErrorCode
 	Message string
 	Details interface{}
+
+	response *Response
 }
 
 func (e Error) Error() string {
 	return fmt.Sprintf("%s (%s)", e.Message, e.Code)
 }
 
+// Response returns the [Response] that contained the error if available.
+func (e Error) Response() *Response {
+	return e.response
+}
+
 // ErrorDetailsInvalidInput contains the details of an 'invalid_input' error.
 type ErrorDetailsInvalidInput struct {
 	Fields []ErrorDetailsInvalidInputField


Test patch:
diff --git a/hcloud/client_test.go b/hcloud/client_test.go
index e4df032..19f4e18 100644
--- a/hcloud/client_test.go
+++ b/hcloud/client_test.go
@@ -85,6 +85,9 @@ func TestClientError(t *testing.T) {
 	if apiError.Message != "An error occurred" {
 		t.Errorf("unexpected error message: %q", apiError.Message)
 	}
+	if apiError.Response().StatusCode != http.StatusUnprocessableEntity {
+		t.Errorf("unexpected http status code: %q", apiError.Response().StatusCode)
+	}
 }
 
 func TestClientInvalidToken(t *testing.T) {