Auto-update branch when merge conflicts (branch behind main); bump to 2.6
Build iOS App / Build .ipa and publish source (push) Successful in 22s

When a PR's head branch is behind main (or conflicts), Gitea's merge returns
409. Previously the app just showed an error. Now: on a 409 the app calls
POST /pulls/{index}/update (merges base into head), then retries the merge
once. Add APIError.mergeConflict for a clear message and map 409 in the
client. Bump to 2.6.
This commit is contained in:
Hermes
2026-08-13 13:02:45 +00:00
parent 0d2c4a3cb6
commit a1ffcaffd2
4 changed files with 38 additions and 4 deletions
+4 -4
View File
@@ -187,7 +187,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 16;
CURRENT_PROJECT_VERSION = 17;
DEVELOPMENT_TEAM = "";
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = "GiteaPR";
@@ -197,7 +197,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 2.5;
MARKETING_VERSION = 2.6;
PRODUCT_BUNDLE_IDENTIFIER = com.bueso.giteapr;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
@@ -211,7 +211,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 16;
CURRENT_PROJECT_VERSION = 17;
DEVELOPMENT_TEAM = "";
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = "GiteaPR";
@@ -221,7 +221,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 2.5;
MARKETING_VERSION = 2.6;
PRODUCT_BUNDLE_IDENTIFIER = com.bueso.giteapr;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
+17
View File
@@ -144,6 +144,23 @@ final class PRDetailViewModel: ObservableObject {
pr = try? await client.pullRequest(owner: owner, repo: repo.name,
number: prNumber)
return true
} catch APIError.mergeConflict {
// Branch is behind main (or conflicts). Update it and retry once.
busyMessage = "Updating branch…"
do {
try await client.updateBranch(owner: owner, repo: repo.name,
number: prNumber)
busyMessage = "Merging…"
try await client.merge(owner: owner, repo: repo.name,
number: prNumber, method: method,
deleteBranchAfterMerge: SettingsStore.shared.deleteBranchAfterMerge)
pr = try? await client.pullRequest(owner: owner, repo: repo.name,
number: prNumber)
return true
} catch {
errorMessage = error.localizedDescription
return false
}
} catch {
errorMessage = error.localizedDescription
return false
+6
View File
@@ -7,6 +7,7 @@ enum APIError: LocalizedError, Equatable {
case decoding(String)
case unauthorized
case notFound
case mergeConflict
var errorDescription: String? {
switch self {
@@ -18,6 +19,9 @@ enum APIError: LocalizedError, Equatable {
if code == 401 || code == 403 {
return "Authentication failed. Check your token."
}
if code == 409 {
return "This branch is out of date or conflicts with the base branch. Update the branch and try again."
}
return message?.isEmpty == false
? "Server error (\(code)): \(message!)"
: "Server error (\(code))."
@@ -27,6 +31,8 @@ enum APIError: LocalizedError, Equatable {
return "Authentication failed. Check your token."
case .notFound:
return "Not found."
case .mergeConflict:
return "This branch is out of date or conflicts with the base branch. Update the branch and try again."
}
}
}
+11
View File
@@ -93,6 +93,9 @@ struct GiteaClient {
case 404:
await LogStore.shared.log(.error, "\(method) \(path) → 404 not found")
throw APIError.notFound
case 409:
await LogStore.shared.log(.warn, "\(method) \(path) → 409 conflict")
throw APIError.mergeConflict
default:
let message = (try? Self.decoder.decode([String: String].self,
from: data))?["message"]
@@ -230,6 +233,14 @@ struct GiteaClient {
method: "DELETE")
}
/// Merge the base branch into the head branch (`POST /pulls/{index}/update`).
/// When a PR is behind `main` (e.g. conflicts), the merge returns 409; this
/// rebases/syncs the head onto the base so it can then be merged.
func updateBranch(owner: String, repo: String, number: Int) async throws {
_ = try await request("repos/\(owner)/\(repo)/pulls/\(number)/update",
method: "POST")
}
func setState(owner: String, repo: String, number: Int,
state: String) async throws {
struct Body: Encodable { let state: String }