import Foundation /// Where a pull stands on landing, in the app's own words. An answer's `reason` /// is written for the command line, with commands to run, so it isn't shown. struct Standing: Equatable { enum Tone { case good, waiting, bad, neutral } let title: String let icon: String let tone: Tone var lines: [String] = [] static func of(_ s: MergeStatus, base: String) -> Standing { let queues = s.may?.queue == true && s.queueBlocked == nil var after: [String] = [] if s.queueBlocked != nil { after.append("The merge queue is off until the owner delegates writes to this server.") } if s.may?.merge != true { after.append("Only the owner and collaborators merge it.") } if s.state == "draft" { return Standing(title: "Still a draft", icon: "git-pull-request-draft", tone: .neutral, lines: ["It can't be merged or queued until it's marked ready for review."]) } if s.state == "conflicting" { return Standing(title: "Conflicts with \(base)", icon: "alert-fill", tone: .bad, lines: ["Merge \(base) into its branch, resolve the files that conflict, then push."]) } if s.reviews == "blocked" { let missing = (s.reviewsMissing ?? []).map { $0.text.prefix(1).uppercased() + $0.text.dropFirst() } return Standing(title: "Review required", icon: "x-circle-fill", tone: .bad, lines: missing + after) } switch s.checks { case "failed": return Standing(title: "Checks failed", icon: "x-circle-fill", tone: .bad, lines: ["It can land once they pass. Fix what failed and push."] + after) case "running": let line = queues ? "It can land once they pass. Queued now, it lands then." : "It can land once they pass." return Standing(title: "Checks are running", icon: "clock", tone: .waiting, lines: [line] + after) case "pending": let line = queues ? "The merge queue checks it against \(base) as it is now, then lands it." : "Its checks must pass against \(base) as it is now first." return Standing(title: "Not checked yet", icon: "clock", tone: .waiting, lines: [line] + after) default: break } if s.queueOnly == true { return Standing(title: "Ready for the merge queue", icon: "check-circle-fill", tone: .good, lines: ["\(base) takes pulls only through the merge queue."] + after) } if s.state == "clean" { return Standing(title: "Ready to merge", icon: "check-circle-fill", tone: .good, lines: after) } return Standing(title: "Not ready to merge", icon: "clock", tone: .waiting, lines: after) } /// One the queue turned away, which can be queued again once fixed. The /// commits it names mean nothing on a phone, and an older runner's reason /// ends with a command to run. static func ejected(reason: String?) -> Standing { var why = reason ?? "" for pattern in [#"[.;] [Tt]heir logs: `knotless run list [^`]*`.*$"#, #"\. Anyone but its author who may write approves it with `knotless comment add .*$"#, #",? on \S+ at [0-9a-f]{7,40}"#, #" at [0-9a-f]{7,40}"#] { why = why.replacingOccurrences(of: pattern, with: "", options: .regularExpression) } let line = why.prefix(1).uppercased() + why.dropFirst() return Standing(title: "Taken out of the merge queue", icon: "x-circle-fill", tone: .bad, lines: line.isEmpty ? [] : [line]) } /// Whether the queue would take it, as the server would answer if asked. static func canQueue(_ s: MergeStatus) -> Bool { s.may?.queue == true && s.queueBlocked == nil && s.state != "conflicting" && s.state != "draft" && s.checks != "failed" && s.reviews != "blocked" } static func canMerge(_ s: MergeStatus) -> Bool { s.may?.merge == true && s.queueOnly != true && s.state == "clean" } }