This repository was archived by the owner on Mar 23, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 9
/
Copy pathGitHub+FileUpdating.swift
142 lines (124 loc) · 5.52 KB
/
GitHub+FileUpdating.swift
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
/*
* Copyright (c) 2017 N26 GmbH.
*
* This file is part of Bob.
*
* Bob is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Bob is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Bob. If not, see <http://www.gnu.org/licenses/>.
*/
import Dispatch
import Foundation
import Vapor
public protocol ItemUpdater {
/// Filters the items to be updated
///
/// - Parameter items: All the items in the three
/// - Returns: The items that should be updated.
func itemsToUpdate(from items: [TreeItem]) -> [TreeItem]
/// Updates the filtered
///
/// - Parameters:
/// - item: The three item
/// - content: The content of the three item as
/// - Returns: The new content of the tree item that will be comitted
func update(_ item: TreeItem, content: String) throws -> String
}
private class BatchItemUpdater {
private let updater: ItemUpdater
private let github: GitHub
init(github: GitHub, updater: ItemUpdater) {
self.github = github
self.updater = updater
}
/// Each item is passed to the `ItemUpdater` to determine if it should be updated.
/// For `ThreeItem` that the `ItemUpdater` wants to update, the content is fetched and passed again to the `ItemUpdater`
/// The new content returned from by the `ItemUpdater` creates a new GitBlob its corresponding `TreeItem`
///
/// - Parameter items: The list of items passed to the Updater
/// - Returns: A future list of `ThreeItem` that were updated
func update(items: [TreeItem], on worker: Worker) throws -> Future<[TreeItem]> {
var updatedFutureItems = [Future<TreeItem>]()
for item in updater.itemsToUpdate(from: items) {
updatedFutureItems.append(try update(item: item))
}
// wait for all items
return updatedFutureItems.flatten(on: worker)
}
private func update(item: TreeItem) throws -> Future<TreeItem> {
let result = try github.gitBlob(sha: item.sha).map(to: String.self) { blob in
guard let content = blob.string else { throw "Could not convert blob content to string" }
return try self.updater.update(item, content: content)
}.flatMap { newContent in
try self.github.newBlob(data: newContent)
}.map { newBlob in
return TreeItem(path: item.path, mode: item.mode, type: item.type, sha: newBlob.sha)
}
return result
}
}
public extension GitHub {
struct CurrentState {
let items: [TreeItem]
let currentCommitSHA: GitHub.Repos.Commit.SHA
let treeSHA: Git.Tree.SHA
}
func currentState(on branch: BranchName) throws -> Future<CurrentState> {
return try assertBranchExists(branch).flatMap { _ -> Future<CurrentState> in
let commitSha = try self.currentCommitSHA(on: branch)
let treeSha = commitSha.flatMap { sha in
try self.treeSHA(forCommitSHA: sha)
}
let tree = treeSha.flatMap { sha in
try self.trees(for: sha)
}
return map(commitSha, treeSha, tree) { commitSha, treeSha, tree in
return CurrentState(items: tree.tree, currentCommitSHA: commitSha, treeSHA: treeSha)
}
}
}
/**
Helper methods that passes the latest files on a specified branch to a ItemUpdate and creates a new commit with the update items/
It
- fetches the current repo at the specified branch
- Passes the file list to the file updater
- Creates a new tree with the update files
- Creates a new commit
*/
func newCommit(updatingItemsWith updater: ItemUpdater, on branch: BranchName, by author: Author, message: String) throws -> Future<GitHub.Git.Reference> {
// Get the repo state
let respositoryState = try currentState(on: branch)
// Pass the items to the updater
let updatedItems = respositoryState.flatMap(to: [TreeItem].self) { respositoryState in
let batchUpdater = BatchItemUpdater(github: self, updater: updater)
return try batchUpdater.update(items: respositoryState.items, on: self.worker)
}
// wait for both repo state and updated items to create a new tree
let newTree = flatMap(to: Tree.self, respositoryState, updatedItems) { respositoryState, updatedItems in
if updatedItems.isEmpty {
throw "The updater \(updater) did not match any items to update"
}
return try self.newTree(tree: Tree.New(baseTree: respositoryState.treeSHA, tree: updatedItems))
}
// wait for both repo state and the new tree
let commit = map(respositoryState, newTree) { state, newTree in
return (state.currentCommitSHA, newTree.sha)
}.flatMap { parentSHA, treeSHA in
// to create a new commit
return try self.newCommit(by: author, message: message, parentSHA: parentSHA, treeSHA: treeSHA)
}.flatMap { newCommit in
// and update the ref
return try self.updateRef(to: newCommit.sha, on: branch)
}
return commit
}
}