-
Notifications
You must be signed in to change notification settings - Fork 7.7k
Expand file tree
/
Copy pathsync.swift
More file actions
executable file
·153 lines (127 loc) · 5.04 KB
/
sync.swift
File metadata and controls
executable file
·153 lines (127 loc) · 5.04 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
#!/usr/bin/swift sh
// Depends on swift-sh. Install with: `brew install swift-sh`
import Foundation
import Cocoa
import AsyncSwiftGit // @bdewey
import OctoKit // nerdishbynature/octokit.swift == main
let createPRs = true
guard CommandLine.arguments.count == 3 else {
print("usage: sync.swift <pull-request-title> <branch-name>")
exit(1)
}
let pullRequestName = CommandLine.arguments[1] // example: "LOOP-4688 DIY Sync"
let syncBranch = CommandLine.arguments[2] // example: "ps/LOOP-4688/diy-sync"
enum EnvError: Error {
case missing(String)
}
func getEnv(_ name: String) throws -> String {
guard let value = ProcessInfo.processInfo.environment[name] else {
throw EnvError.missing(name)
}
return value
}
let ghUsername = try getEnv("GH_USERNAME")
let ghToken = try getEnv("GH_TOKEN")
let ghCommitterName = try getEnv("GH_COMMITTER_NAME")
let ghCommitterEmail = try getEnv("GH_COMMITTER_EMAIL")
struct Project {
let project: String
let branch: String
let subdir: String
init(_ project: String, _ branch: String, _ subdir: String = "") {
self.project = project
self.branch = branch
self.subdir = subdir
}
var path: String {
if subdir.isEmpty {
return project
} else {
return subdir + "/" + project
}
}
}
let projects = [
Project("Loop", "dev"),
Project("LoopKit", "dev"),
Project("TidepoolService", "dev"),
Project("CGMBLEKit", "dev"),
Project("dexcom-share-client-swift", "dev"),
Project("RileyLinkKit", "dev"),
Project("NightscoutService", "dev"),
Project("LoopOnboarding", "dev"),
Project("AmplitudeService", "dev"),
Project("LogglyService", "dev"),
Project("MixpanelService", "main"),
Project("OmniBLE", "dev"),
Project("NightscoutRemoteCGM", "dev"),
Project("LoopSupport", "dev"),
Project("G7SensorKit", "main"),
Project("OmniKit", "main"),
Project("MinimedKit", "main"),
Project("LibreTransmitter", "main")
]
let fm = FileManager.default
let loopkit = URL(string: "https://github.com/LoopKit")!
let tidepool = URL(string: "https://github.com/tidepool-org")!
let incomingRemote = "tidepool"
let octokit = Octokit(TokenConfiguration(ghToken))
let credentials = Credentials.plaintext(username: ghUsername, password: ghToken)
let signature = try! Signature(name: ghCommitterName, email: ghCommitterEmail)
for project in projects {
let dest = URL(string: fm.currentDirectoryPath)!.appendingPathComponent(project.path)
let repository: AsyncSwiftGit.Repository
if !fm.fileExists(atPath: dest.path) {
print("Cloning \(project.project)")
let url = loopkit.appendingPathComponent(project.project)
repository = try await Repository.clone(from: url, to: dest)
print("Cloned \(project.project)")
} else {
print("Already Exists: \(project.path)")
repository = try Repository(openAt: dest)
}
let incomingRemoteURL = tidepool.appendingPathComponent(project.project)
// Add remote if it doesn't exist, and fetch latest changes
if (try? repository.remoteURL(for: incomingRemote)) == nil {
try repository.addRemote(incomingRemote, url: incomingRemoteURL)
}
try await repository.fetch(remote: incomingRemote)
// Create and checkout the branch where sync changesets will go ("tidepool-sync")
if !(try repository.branchExists(named: syncBranch)) {
try repository.createBranch(named: syncBranch, target: "origin/\(project.branch)")
}
try await repository.checkout(revspec: syncBranch)
// Merge changes from tidepool to diy
print("Starting merge for \(project.project)")
try await repository.merge(revisionSpecification: "\(incomingRemote)/\(project.branch)", signature: signature)
let originTree = try repository.lookupTree(for: "origin/\(project.branch)")
let diff = try repository.diff(originTree, repository.headTree)
guard diff.count > 0 else {
print("No incoming changes; skipping PR creation.")
try await repository.checkout(revspec: project.branch)
continue
}
print("Found diffs: \(diff)")
// Push changes up to origin
let refspec = "refs/heads/" + syncBranch + ":refs/heads/" + syncBranch
print("Pushing \(refspec) to \(project.project)")
try await repository.push(remoteName: "origin", refspecs: [refspec], credentials: credentials)
if createPRs {
// Make sure a PR exists, or create it
let prs = try await octokit.pullRequests(owner: "LoopKit", repository: project.project, base: project.branch, head:"LoopKit:" + syncBranch)
let pr: PullRequest
if prs.count == 0 {
pr = try await octokit.createPullRequest(owner: "LoopKit", repo: project.project, title: pullRequestName, head: "LoopKit:" + syncBranch, base: project.branch, body: "")
print("PR = \(pr)")
} else {
pr = prs.first!
}
if let url = pr.htmlURL {
if NSWorkspace.shared.open(url) {
print("default browser was successfully opened")
}
}
} else {
print("Skipping PR creation")
}
}