| 2218 | } |
| 2219 | |
| 2220 | func UpdateIssue(ctx context.Context, client *github.Client, gqlClient *githubv4.Client, owner string, repo string, issueNumber int, title string, body string, assignees []string, labels []string, milestoneNum int, issueType string, issueFieldValues []*github.IssueRequestFieldValue, fieldIDsToDelete []int64, state string, stateReason string, duplicateOf int, opts ...UpdateIssueOptions) (*mcp.CallToolResult, error) { |
| 2221 | updateOptions := UpdateIssueOptions{ |
| 2222 | AssigneesProvided: len(assignees) > 0, |
| 2223 | LabelsProvided: len(labels) > 0, |
| 2224 | } |
| 2225 | for _, opt := range opts { |
| 2226 | updateOptions.AssigneesProvided = updateOptions.AssigneesProvided || opt.AssigneesProvided |
| 2227 | updateOptions.LabelsProvided = updateOptions.LabelsProvided || opt.LabelsProvided |
| 2228 | } |
| 2229 | |
| 2230 | // Create the issue request with only provided fields |
| 2231 | issueRequest := &github.IssueRequest{} |
| 2232 | |
| 2233 | // Set optional parameters if provided |
| 2234 | if title != "" { |
| 2235 | issueRequest.Title = github.Ptr(title) |
| 2236 | } |
| 2237 | |
| 2238 | if body != "" { |
| 2239 | issueRequest.Body = github.Ptr(body) |
| 2240 | } |
| 2241 | |
| 2242 | if updateOptions.LabelsProvided { |
| 2243 | issueRequest.Labels = &labels |
| 2244 | } |
| 2245 | |
| 2246 | if updateOptions.AssigneesProvided { |
| 2247 | issueRequest.Assignees = &assignees |
| 2248 | } |
| 2249 | |
| 2250 | if milestoneNum != 0 { |
| 2251 | issueRequest.Milestone = &milestoneNum |
| 2252 | } |
| 2253 | |
| 2254 | if issueType != "" { |
| 2255 | issueRequest.Type = github.Ptr(issueType) |
| 2256 | } |
| 2257 | |
| 2258 | // Field IDs to clear via DELETE after the PATCH. See the post-PATCH loop |
| 2259 | // for why we can't just rely on REST set semantics. |
| 2260 | var fallbackDeleteFieldIDs []int64 |
| 2261 | |
| 2262 | if len(issueFieldValues) > 0 || len(fieldIDsToDelete) > 0 { |
| 2263 | // REST PATCH uses set semantics, so fetch existing values, merge in |
| 2264 | // the new ones, then drop anything explicitly deleted. |
| 2265 | existing, err := fetchExistingIssueFieldValues(ctx, gqlClient, owner, repo, issueNumber) |
| 2266 | if err != nil { |
| 2267 | return ghErrors.NewGitHubGraphQLErrorResponse(ctx, "failed to fetch existing issue field values", err), nil |
| 2268 | } |
| 2269 | merged := mergeIssueFieldValues(existing, issueFieldValues) |
| 2270 | if len(fieldIDsToDelete) > 0 { |
| 2271 | deleteSet := make(map[int64]bool, len(fieldIDsToDelete)) |
| 2272 | for _, id := range fieldIDsToDelete { |
| 2273 | deleteSet[id] = true |
| 2274 | } |
| 2275 | kept := make([]*github.IssueRequestFieldValue, 0, len(merged)) |
| 2276 | for _, v := range merged { |
| 2277 | if !deleteSet[v.FieldID] { |