Skip to content

Commit 24e6477

Browse files
author
Sharma Elanthiriayan
committed
Merge branch 'develop' into issue/4142-migrate-settings-screen-to-mvvm
2 parents af44cd8 + 7e97394 commit 24e6477

File tree

69 files changed

+2767
-535
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

69 files changed

+2767
-535
lines changed

Fakes/Fakes/Networking.generated.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1314,6 +1314,8 @@ extension Site {
13141314
description: .fake(),
13151315
url: .fake(),
13161316
plan: .fake(),
1317+
isJetpackThePluginInstalled: .fake(),
1318+
isJetpackConnected: .fake(),
13171319
isWooCommerceActive: .fake(),
13181320
isWordPressStore: .fake(),
13191321
timezone: .fake(),

Hardware/Hardware.xcodeproj/project.pbxproj

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
/* Begin PBXBuildFile section */
1010
030338102705F7D400764131 /* ReceiptTotalLine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0303380F2705F7D400764131 /* ReceiptTotalLine.swift */; };
1111
311889EB2653286B0080AEA2 /* PaymentIntentMetadataTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 311889EA2653286B0080AEA2 /* PaymentIntentMetadataTests.swift */; };
12+
55CD4BB4273E617C007686D3 /* ReceiptRendererTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55CD4BB3273E617C007686D3 /* ReceiptRendererTest.swift */; };
1213
5A747BE9FA06EC8752A35752 /* Pods_HardwareTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B1DC5B6141B8184FAC29B0A4 /* Pods_HardwareTests.framework */; };
1314
8FFAA245E257B9EB98E2FCBD /* Pods_SampleReceiptPrinter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2AFA997D6786C67B0A061854 /* Pods_SampleReceiptPrinter.framework */; };
1415
C5D2CB7D21CEE28FEBF18BF6 /* Pods_Hardware.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E9F0AC202B287C1221EA2C99 /* Pods_Hardware.framework */; };
@@ -134,6 +135,7 @@
134135
311889EA2653286B0080AEA2 /* PaymentIntentMetadataTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentIntentMetadataTests.swift; sourceTree = "<group>"; };
135136
3CF9348BADD5E0518080A61A /* Pods-Hardware.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Hardware.debug.xcconfig"; path = "Target Support Files/Pods-Hardware/Pods-Hardware.debug.xcconfig"; sourceTree = "<group>"; };
136137
47BDD50287B7B0CF8D769BFC /* Pods-PrinterPlayground.release-alpha.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PrinterPlayground.release-alpha.xcconfig"; path = "Target Support Files/Pods-PrinterPlayground/Pods-PrinterPlayground.release-alpha.xcconfig"; sourceTree = "<group>"; };
138+
55CD4BB3273E617C007686D3 /* ReceiptRendererTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReceiptRendererTest.swift; sourceTree = "<group>"; };
137139
9726331F55A9621F2F887E13 /* Pods-Hardware.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Hardware.release.xcconfig"; path = "Target Support Files/Pods-Hardware/Pods-Hardware.release.xcconfig"; sourceTree = "<group>"; };
138140
AE60F16D43C20AD4523A61A5 /* Pods-SampleReceiptPrinter.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SampleReceiptPrinter.debug.xcconfig"; path = "Target Support Files/Pods-SampleReceiptPrinter/Pods-SampleReceiptPrinter.debug.xcconfig"; sourceTree = "<group>"; };
139141
B1DC5B6141B8184FAC29B0A4 /* Pods_HardwareTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_HardwareTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -258,6 +260,14 @@
258260
/* End PBXFrameworksBuildPhase section */
259261

260262
/* Begin PBXGroup section */
263+
55CD4BB2273E6159007686D3 /* AirPrintReceipt */ = {
264+
isa = PBXGroup;
265+
children = (
266+
55CD4BB3273E617C007686D3 /* ReceiptRendererTest.swift */,
267+
);
268+
path = AirPrintReceipt;
269+
sourceTree = "<group>";
270+
};
261271
ADED522B787D093F499BD540 /* Pods */ = {
262272
isa = PBXGroup;
263273
children = (
@@ -351,6 +361,7 @@
351361
D88FDB0C25DD216B00CB0DBD /* HardwareTests */ = {
352362
isa = PBXGroup;
353363
children = (
364+
55CD4BB2273E6159007686D3 /* AirPrintReceipt */,
354365
D845BDF3262DD65D00A3E40F /* Mocks */,
355366
D88FDB0F25DD216B00CB0DBD /* Info.plist */,
356367
D892F21325E3F67A001D7134 /* CardReaderTests.swift */,
@@ -566,6 +577,7 @@
566577
};
567578
D88FDB0725DD216B00CB0DBD = {
568579
CreatedOnToolsVersion = 12.4;
580+
LastSwiftMigration = 1310;
569581
};
570582
E1CFC14A2643E9EE0089F86F = {
571583
CreatedOnToolsVersion = 12.4;
@@ -797,6 +809,7 @@
797809
D81AE85E25E6A89F00D9CFD3 /* StripeCardReaderCacheTests.swift in Sources */,
798810
D892F21425E3F67A001D7134 /* CardReaderTests.swift in Sources */,
799811
D854FC26260A6B5800A219CD /* ErrorCodesTests.swift in Sources */,
812+
55CD4BB4273E617C007686D3 /* ReceiptRendererTest.swift in Sources */,
800813
D892F21025E3F4C0001D7134 /* MockStripeCardReader.swift in Sources */,
801814
D88303DB25E450E700C877F9 /* PaymentIntentTests.swift in Sources */,
802815
D845BE03262DDBF500A3E40F /* CardBrandTests.swift in Sources */,
@@ -1019,6 +1032,7 @@
10191032
baseConfigurationReference = 1CC96A71F2937BCB7432766F /* Pods-HardwareTests.debug.xcconfig */;
10201033
buildSettings = {
10211034
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "${inherited}";
1035+
CLANG_ENABLE_MODULES = YES;
10221036
CODE_SIGN_IDENTITY = "iPhone Developer";
10231037
CODE_SIGN_STYLE = Automatic;
10241038
DEVELOPMENT_TEAM = PZYM8XX95Q;
@@ -1030,6 +1044,7 @@
10301044
);
10311045
PRODUCT_BUNDLE_IDENTIFIER = com.automattic.Hardware.HardwareTests;
10321046
PRODUCT_NAME = "$(TARGET_NAME)";
1047+
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
10331048
SWIFT_VERSION = 5.0;
10341049
TARGETED_DEVICE_FAMILY = "1,2";
10351050
};
@@ -1040,6 +1055,7 @@
10401055
baseConfigurationReference = C61D1642BE09D1A1AD6AA9FA /* Pods-HardwareTests.release.xcconfig */;
10411056
buildSettings = {
10421057
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "${inherited}";
1058+
CLANG_ENABLE_MODULES = YES;
10431059
CODE_SIGN_IDENTITY = "iPhone Developer";
10441060
CODE_SIGN_STYLE = Automatic;
10451061
DEVELOPMENT_TEAM = PZYM8XX95Q;
@@ -1147,6 +1163,7 @@
11471163
baseConfigurationReference = DB2B86965868C0EC25910D7D /* Pods-HardwareTests.release-alpha.xcconfig */;
11481164
buildSettings = {
11491165
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = "${inherited}";
1166+
CLANG_ENABLE_MODULES = YES;
11501167
CODE_SIGN_IDENTITY = "iPhone Developer";
11511168
CODE_SIGN_STYLE = Automatic;
11521169
DEVELOPMENT_TEAM = PZYM8XX95Q;

Hardware/Hardware/Printer/AirPrintReceipt/ReceiptRenderer.swift

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,8 @@ private extension ReceiptRenderer {
110110
private func summaryTable() -> String {
111111
var summaryContent = "<table>"
112112
for line in content.lineItems {
113-
summaryContent += "<tr><td>\(line.title) × \(line.quantity)</td><td>\(line.amount) \(content.parameters.currency.uppercased())</td></tr>"
113+
let stripedTitle = line.title.htmlStripped()
114+
summaryContent += "<tr><td>\(stripedTitle) × \(line.quantity)</td><td>\(line.amount) \(content.parameters.currency.uppercased())</td></tr>"
114115
}
115116
summaryContent += totalRows()
116117
summaryContent += "</table>"
@@ -158,9 +159,9 @@ private extension ReceiptRenderer {
158159
/// are required in the US.
159160
/// https://stripe.com/docs/terminal/checkout/receipts#custom
160161
return """
161-
\(Localization.applicationName): \(emv.applicationPreferredName)<br/>
162-
\(Localization.aid): \(emv.dedicatedFileName)
163-
"""
162+
\(Localization.applicationName): \(emv.applicationPreferredName.htmlStripped())<br/>
163+
\(Localization.aid): \(emv.dedicatedFileName.htmlStripped())
164+
"""
164165
}
165166

166167
private func cardIconCSS() -> String {
@@ -250,3 +251,19 @@ private extension ReceiptRenderer {
250251
)
251252
}
252253
}
254+
255+
private extension String {
256+
func htmlStripped() -> String {
257+
let data = Data(utf8)
258+
do {
259+
return try NSAttributedString(
260+
data: data,
261+
options: [.documentType: NSAttributedString.DocumentType.html],
262+
documentAttributes: nil
263+
).string
264+
} catch {
265+
DDLogError("Failed to remove HTML from \(self): \(error.localizedDescription)")
266+
return self
267+
}
268+
}
269+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import XCTest
2+
@testable import Hardware
3+
import StripeTerminal
4+
import Foundation
5+
import CryptoKit
6+
7+
final class ReceiptRendererTest: XCTestCase {
8+
func test_TextWithoutHtmlSymbols() {
9+
let expectedResultWithoutHtmlSymbolsMd5Description = "MD5 digest: 532a92e85527596a692b4b9ee7c978a5"
10+
let content = generateReceiptContent()
11+
12+
let renderer = ReceiptRenderer(content: content)
13+
14+
XCTAssertEqual(
15+
Insecure.MD5.hash(data: renderer.htmlContent().data(using: .utf8)!).description,
16+
expectedResultWithoutHtmlSymbolsMd5Description
17+
)
18+
}
19+
20+
func test_TextWithHtmlSymbols() {
21+
let expectedResultWithHtmlSymbolsMd5Description = "MD5 digest: 034ae681824b18c473c9ca9ad69a06bb"
22+
let stringWithHtml = "<tt><table></table></footer>"
23+
let content = generateReceiptContent(stringToAppend: stringWithHtml)
24+
25+
let renderer = ReceiptRenderer(content: content)
26+
27+
print(renderer.htmlContent())
28+
29+
XCTAssertEqual(
30+
Insecure.MD5.hash(data: renderer.htmlContent().data(using: .utf8)!).description,
31+
expectedResultWithHtmlSymbolsMd5Description
32+
)
33+
}
34+
}
35+
36+
private extension ReceiptRendererTest {
37+
func generateReceiptContent(stringToAppend: String = "") -> ReceiptContent {
38+
ReceiptContent(
39+
parameters: CardPresentReceiptParameters(
40+
amount: 1,
41+
formattedAmount: "1",
42+
currency: "USD",
43+
date: .init(),
44+
storeName: "Test Store",
45+
cardDetails: .init(
46+
last4: "1234",
47+
expMonth: 12,
48+
expYear: 26,
49+
cardholderName: "John Smith",
50+
brand: .masterCard,
51+
fingerprint: "fpr*****",
52+
generatedCard: "pm_******",
53+
receipt: .init(
54+
applicationPreferredName: "Stripe Credit\(stringToAppend)",
55+
dedicatedFileName: "A00000000000000\(stringToAppend)",
56+
authorizationResponseCode: "0000",
57+
applicationCryptogram: "XXXXXXXXXXXX",
58+
terminalVerificationResults: "101010101010101010",
59+
transactionStatusInformation: "6800",
60+
accountType: "credit"
61+
),
62+
emvAuthData: "AD*******"),
63+
orderID: 9201
64+
),
65+
lineItems: [ReceiptLineItem(title: "Sample product #1\(stringToAppend)", quantity: "2", amount: "25")],
66+
cartTotals: [ReceiptTotalLine(description: "description", amount: "13")],
67+
orderNote: nil
68+
)
69+
}
70+
}

Networking/Networking/Model/Copiable/Models+Copiable.generated.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1151,6 +1151,8 @@ extension Site {
11511151
description: CopiableProp<String> = .copy,
11521152
url: CopiableProp<String> = .copy,
11531153
plan: CopiableProp<String> = .copy,
1154+
isJetpackThePluginInstalled: CopiableProp<Bool> = .copy,
1155+
isJetpackConnected: CopiableProp<Bool> = .copy,
11541156
isWooCommerceActive: CopiableProp<Bool> = .copy,
11551157
isWordPressStore: CopiableProp<Bool> = .copy,
11561158
timezone: CopiableProp<String> = .copy,
@@ -1161,6 +1163,8 @@ extension Site {
11611163
let description = description ?? self.description
11621164
let url = url ?? self.url
11631165
let plan = plan ?? self.plan
1166+
let isJetpackThePluginInstalled = isJetpackThePluginInstalled ?? self.isJetpackThePluginInstalled
1167+
let isJetpackConnected = isJetpackConnected ?? self.isJetpackConnected
11641168
let isWooCommerceActive = isWooCommerceActive ?? self.isWooCommerceActive
11651169
let isWordPressStore = isWordPressStore ?? self.isWordPressStore
11661170
let timezone = timezone ?? self.timezone
@@ -1172,6 +1176,8 @@ extension Site {
11721176
description: description,
11731177
url: url,
11741178
plan: plan,
1179+
isJetpackThePluginInstalled: isJetpackThePluginInstalled,
1180+
isJetpackConnected: isJetpackConnected,
11751181
isWooCommerceActive: isWooCommerceActive,
11761182
isWordPressStore: isWordPressStore,
11771183
timezone: timezone,

Networking/Networking/Model/Site.swift

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,14 @@ public struct Site: Decodable, Equatable, GeneratedFakeable, GeneratedCopiable {
2525
///
2626
public let plan: String
2727

28+
/// Whether the site has Jetpack-the-plugin installed.
29+
///
30+
public let isJetpackThePluginInstalled: Bool
31+
32+
/// Whether the site is connected to Jetpack, either through Jetpack-the-plugin or other plugins that include Jetpack Connection Package.
33+
///
34+
public let isJetpackConnected: Bool
35+
2836
/// Indicates if there is a WooCommerce Store Active.
2937
///
3038
public let isWooCommerceActive: Bool
@@ -50,6 +58,8 @@ public struct Site: Decodable, Equatable, GeneratedFakeable, GeneratedCopiable {
5058
let name = try siteContainer.decode(String.self, forKey: .name)
5159
let description = try siteContainer.decode(String.self, forKey: .description)
5260
let url = try siteContainer.decode(String.self, forKey: .url)
61+
let isJetpackThePluginInstalled = try siteContainer.decode(Bool.self, forKey: .isJetpackThePluginInstalled)
62+
let isJetpackConnected = try siteContainer.decode(Bool.self, forKey: .isJetpackConnected)
5363

5464
let optionsContainer = try siteContainer.nestedContainer(keyedBy: OptionKeys.self, forKey: .options)
5565
let isWordPressStore = try optionsContainer.decode(Bool.self, forKey: .isWordPressStore)
@@ -62,6 +72,8 @@ public struct Site: Decodable, Equatable, GeneratedFakeable, GeneratedCopiable {
6272
description: description,
6373
url: url,
6474
plan: String(), // Not created on init. Added in supplementary API request.
75+
isJetpackThePluginInstalled: isJetpackThePluginInstalled,
76+
isJetpackConnected: isJetpackConnected,
6577
isWooCommerceActive: isWooCommerceActive,
6678
isWordPressStore: isWordPressStore,
6779
timezone: timezone,
@@ -75,6 +87,8 @@ public struct Site: Decodable, Equatable, GeneratedFakeable, GeneratedCopiable {
7587
description: String,
7688
url: String,
7789
plan: String,
90+
isJetpackThePluginInstalled: Bool,
91+
isJetpackConnected: Bool,
7892
isWooCommerceActive: Bool,
7993
isWordPressStore: Bool,
8094
timezone: String,
@@ -84,13 +98,22 @@ public struct Site: Decodable, Equatable, GeneratedFakeable, GeneratedCopiable {
8498
self.description = description
8599
self.url = url
86100
self.plan = plan
101+
self.isJetpackThePluginInstalled = isJetpackThePluginInstalled
102+
self.isJetpackConnected = isJetpackConnected
87103
self.isWordPressStore = isWordPressStore
88104
self.isWooCommerceActive = isWooCommerceActive
89105
self.timezone = timezone
90106
self.gmtOffset = gmtOffset
91107
}
92108
}
93109

110+
public extension Site {
111+
/// Whether the site is connected to Jetpack with Jetpack Connection Package, and not with Jetpack-the-plugin.
112+
///
113+
var isJetpackCPConnected: Bool {
114+
isJetpackConnected && !isJetpackThePluginInstalled
115+
}
116+
}
94117

95118
/// Defines all of the Site CodingKeys.
96119
///
@@ -103,6 +126,8 @@ private extension Site {
103126
case url = "URL"
104127
case options = "options"
105128
case plan = "plan"
129+
case isJetpackThePluginInstalled = "jetpack"
130+
case isJetpackConnected = "jetpack_connection"
106131
}
107132

108133
enum OptionKeys: String, CodingKey {

Networking/Networking/Remote/AccountRemote.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ public class AccountRemote: Remote {
5353
public func loadSites(completion: @escaping (Result<[Site], Error>) -> Void) {
5454
let path = "me/sites"
5555
let parameters = [
56-
"fields": "ID,name,description,URL,options",
56+
"fields": "ID,name,description,URL,options,jetpack,jetpack_connection",
5757
"options": "timezone,is_wpcom_store,woocommerce_is_active,gmt_offset"
5858
]
5959

Networking/NetworkingTests/Responses/sites-malformed.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"description": "Testing Tagline",
77
"URL": "https:\/\/some-testing-url.testing.blog",
88
"jetpack": true,
9+
"jetpack_connection": true,
910
"updates": {
1011
"plugins": 3,
1112
"themes": 1,
@@ -20,6 +21,7 @@
2021
"description": "Your Favorite Blog",
2122
"URL": "https:\/\/thoughts.testing.blog",
2223
"jetpack": false,
24+
"jetpack_connection": true,
2325
"options": {
2426
"timezone": "",
2527
"gmt_offset": -4,

Networking/NetworkingTests/Responses/sites.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"description": "Testing Tagline",
77
"URL": "https:\/\/some-testing-url.testing.blog",
88
"jetpack": true,
9+
"jetpack_connection": true,
910
"options": {
1011
"timezone": "",
1112
"gmt_offset": 3.5,
@@ -144,6 +145,7 @@
144145
"description": "Your Favorite Blog",
145146
"URL": "https:\/\/thoughts.testing.blog",
146147
"jetpack": false,
148+
"jetpack_connection": true,
147149
"options": {
148150
"timezone": "",
149151
"gmt_offset": -4,

Storage/Storage.xcodeproj/project.pbxproj

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,7 @@
245245
028296F1237D404F00E84012 /* GenericAttribute+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GenericAttribute+CoreDataProperties.swift"; sourceTree = "<group>"; };
246246
0284BD8725BAC75500D00C06 /* Model 43.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Model 43.xcdatamodel"; sourceTree = "<group>"; };
247247
0284BD8825BAC83700D00C06 /* WooCommerceModelV42toV43.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = WooCommerceModelV42toV43.xcmappingmodel; sourceTree = "<group>"; };
248+
028A81022740E1B400F04D15 /* Model 58.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Model 58.xcdatamodel"; sourceTree = "<group>"; };
248249
028F00652331605000E6C283 /* Model 20.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Model 20.xcdatamodel"; sourceTree = "<group>"; };
249250
029DE8AD25C115860046EFF5 /* Model 44.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Model 44.xcdatamodel"; sourceTree = "<group>"; };
250251
02A098262480D160002F8C7A /* MockCrashLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockCrashLogger.swift; sourceTree = "<group>"; };
@@ -1692,6 +1693,7 @@
16921693
B59E11D820A9D00C004121A4 /* WooCommerce.xcdatamodeld */ = {
16931694
isa = XCVersionGroup;
16941695
children = (
1696+
028A81022740E1B400F04D15 /* Model 58.xcdatamodel */,
16951697
313FC0B62732EE520067408D /* Model 57.xcdatamodel */,
16961698
31C1D22D2727204200EDEE49 /* Model 56.xcdatamodel */,
16971699
DE126D1026CE5A28007F901D /* Model 55.xcdatamodel */,
@@ -1750,7 +1752,7 @@
17501752
746A9D14214071F90013F6FF /* Model 2.xcdatamodel */,
17511753
B59E11D920A9D00C004121A4 /* Model.xcdatamodel */,
17521754
);
1753-
currentVersion = 313FC0B62732EE520067408D /* Model 57.xcdatamodel */;
1755+
currentVersion = 028A81022740E1B400F04D15 /* Model 58.xcdatamodel */;
17541756
path = WooCommerce.xcdatamodeld;
17551757
sourceTree = "<group>";
17561758
versionGroupType = wrapper.xcdatamodel;

0 commit comments

Comments
 (0)