Repository: cli/cli (go)

Issue:
`gh project item-edit` should allow explicit `--number 0` field values
### Describe the bug

> Here's another case that I found out for `--number` that 0 is discarded as an invalid value:
> 
> ```shell
> $ bin/gh project item-edit --project-id $projectId --id $itemId --field-id $fieldId --number 0
> error: no changes to make
> ```
> 
> However, given above valid ranges, it is a valid value and can be set from UI:
> 
> ![Image](https://github.com/user-attachments/assets/9b1591dc-82f8-4a5d-a3e5-2fb5f685a111)

 _Originally posted by @iamazeem in [#10342](https://github.com/cli/cli/issues/10342#issuecomment-2639592210)_

As stated in the issue, this appears to prevent users from accidentally setting item field values to zero due to zero value for float flags:

https://github.com/cli/cli/blob/756ba75c9ded6d1d201ddef4f271f4ba37057659/pkg/cmd/project/item-edit/item_edit.go#L79-L96

https://github.com/cli/cli/blob/756ba75c9ded6d1d201ddef4f271f4ba37057659/pkg/cmd/project/item-edit/item_edit.go#L148-L155

### Affected version

`2.66.1`

### Steps to reproduce the behavior

1. Create v2 project with `Number` fields
2. Add issue to project
3. `gh project item-edit --project-id $projectId --id $itemId --field-id $fieldId --number 0`

### Expected vs actual behavior

When a user calls `gh project item-edit` and `--number 0` is explicitly set, then we allow the item field value to be set to zero.

### Logs

```shell
$ GH_DEBUG=api ./bin/gh project item-edit --project-id PVT_kwDOBWmBn84Al2QA --id PVTI_lADOBWmBn84Al2QAzgXCZ4c --field-id PVTF_lADOBWmBn84Al2QAzgnpmgo --number 0                    
error: no changes to make
```

Pull request description:
Fix gh project item-edit to allow --number 0 as a valid value
Fixes #10380 

Patch:
diff --git a/pkg/cmd/project/item-edit/item_edit.go b/pkg/cmd/project/item-edit/item_edit.go
index c4acaaea9..38818b5c5 100644
--- a/pkg/cmd/project/item-edit/item_edit.go
+++ b/pkg/cmd/project/item-edit/item_edit.go
@@ -24,6 +24,7 @@ type editItemOpts struct {
 	projectID            string
 	text                 string
 	number               float64
+	numberChanged        bool
 	date                 string
 	singleSelectOptionID string
 	iterationID          string
@@ -76,10 +77,11 @@ func NewCmdEditItem(f *cmdutil.Factory, runF func(config editItemConfig) error)
 			gh project item-edit --id <item-ID> --field-id <field-ID> --project-id <project-ID> --clear
 		`),
 		RunE: func(cmd *cobra.Command, args []string) error {
+			opts.numberChanged = cmd.Flags().Changed("number")
 			if err := cmdutil.MutuallyExclusive(
 				"only one of `--text`, `--number`, `--date`, `--single-select-option-id` or `--iteration-id` may be used",
 				opts.text != "",
-				opts.number != 0,
+				opts.numberChanged,
 				opts.date != "",
 				opts.singleSelectOptionID != "",
 				opts.iterationID != "",
@@ -89,7 +91,7 @@ func NewCmdEditItem(f *cmdutil.Factory, runF func(config editItemConfig) error)
 
 			if err := cmdutil.MutuallyExclusive(
 				"cannot use `--text`, `--number`, `--date`, `--single-select-option-id` or `--iteration-id` in conjunction with `--clear`",
-				opts.text != "" || opts.number != 0 || opts.date != "" || opts.singleSelectOptionID != "" || opts.iterationID != "",
+				opts.text != "" || opts.numberChanged || opts.date != "" || opts.singleSelectOptionID != "" || opts.iterationID != "",
 				opts.clear,
 			); err != nil {
 				return err
@@ -146,7 +148,7 @@ func runEditItem(config editItemConfig) error {
 	}
 
 	// update item values
-	if config.opts.text != "" || config.opts.number != 0 || config.opts.date != "" || config.opts.singleSelectOptionID != "" || config.opts.iterationID != "" {
+	if config.opts.text != "" || config.opts.numberChanged || config.opts.date != "" || config.opts.singleSelectOptionID != "" || config.opts.iterationID != "" {
 		return updateItemValues(config)
 	}
 
@@ -172,7 +174,7 @@ func buildUpdateItem(config editItemConfig, date time.Time) (*UpdateProjectV2Fie
 		value = githubv4.ProjectV2FieldValue{
 			Text: githubv4.NewString(githubv4.String(config.opts.text)),
 		}
-	} else if config.opts.number != 0 {
+	} else if config.opts.numberChanged {
 		value = githubv4.ProjectV2FieldValue{
 			Number: githubv4.NewFloat(githubv4.Float(config.opts.number)),
 		}


Test patch:
diff --git a/pkg/cmd/project/item-edit/item_edit_test.go b/pkg/cmd/project/item-edit/item_edit_test.go
index 2f9a16df6..916bb5899 100644
--- a/pkg/cmd/project/item-edit/item_edit_test.go
+++ b/pkg/cmd/project/item-edit/item_edit_test.go
@@ -55,6 +55,14 @@ func TestNewCmdeditItem(t *testing.T) {
 				itemID: "123",
 			},
 		},
+		{
+			name: "number zero",
+			cli:  "--number 0 --id 123",
+			wants: editItemOpts{
+				number: 0,
+				itemID: "123",
+			},
+		},
 		{
 			name: "field-id",
 			cli:  "--field-id FIELD_ID --id 123",
@@ -292,10 +300,64 @@ func TestRunItemEdit_Number(t *testing.T) {
 	config := editItemConfig{
 		io: ios,
 		opts: editItemOpts{
-			number:    123.45,
-			itemID:    "item_id",
-			projectID: "project_id",
-			fieldID:   "field_id",
+			number:        123.45,
+			numberChanged: true,
+			itemID:        "item_id",
+			projectID:     "project_id",
+			fieldID:       "field_id",
+		},
+		client: client,
+	}
+
+	err := runEditItem(config)
+	assert.NoError(t, err)
+	assert.Equal(
+		t,
+		"Edited item \"title\"\n",
+		stdout.String())
+}
+
+func TestRunItemEdit_NumberZero(t *testing.T) {
+	defer gock.Off()
+	// gock.Observe(gock.DumpRequest)
+
+	// edit item
+	gock.New("https://api.github.com").
+		Post("/graphql").
+		BodyString(`{"query":"mutation UpdateItemValues.*","variables":{"input":{"projectId":"project_id","itemId":"item_id","fieldId":"field_id","value":{"number":0}}}}`).
+		Reply(200).
+		JSON(map[string]interface{}{
+			"data": map[string]interface{}{
+				"updateProjectV2ItemFieldValue": map[string]interface{}{
+					"projectV2Item": map[string]interface{}{
+						"ID": "item_id",
+						"content": map[string]interface{}{
+							"__typename": "Issue",
+							"body":       "body",
+							"title":      "title",
+							"number":     1,
+							"repository": map[string]interface{}{
+								"nameWithOwner": "my-repo",
+							},
+						},
+					},
+				},
+			},
+		})
+
+	client := queries.NewTestClient()
+
+	ios, _, stdout, _ := iostreams.Test()
+	ios.SetStdoutTTY(true)
+
+	config := editItemConfig{
+		io: ios,
+		opts: editItemOpts{
+			number:        0,
+			numberChanged: true,
+			itemID:        "item_id",
+			projectID:     "project_id",
+			fieldID:       "field_id",
 		},
 		client: client,
 	}