-
Notifications
You must be signed in to change notification settings - Fork 1.3k
/
Git.swift
162 lines (138 loc) · 5.65 KB
/
Git.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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
/*
This source file is part of the Swift.org open source project
Copyright 2015 - 2016 Apple Inc. and the Swift project authors
Licensed under Apache License v2.0 with Runtime Library Exception
See http://swift.org/LICENSE.txt for license information
See http://swift.org/CONTRIBUTORS.txt for Swift project authors
*/
import Basic
import func POSIX.realpath
import func POSIX.getenv
import libc
import class Foundation.ProcessInfo
import struct PackageDescription.Version
extension Version {
static func vprefix(_ string: String) -> Version? {
if string.characters.first == "v" {
return Version(string.characters.dropFirst())
} else {
return nil
}
}
}
public class Git {
public class Repo {
public let path: AbsolutePath
public init?(path: AbsolutePath) {
self.path = resolveSymlinks(path)
guard isDirectory(path.appending(component: ".git")) else { return nil }
}
public lazy var origin: String? = { repo in
do {
guard let url = try Git.runPopen([Git.tool, "-C", repo.path.asString, "config", "--get", "remote.origin.url"]).chuzzle() else {
return nil
}
if URL.scheme(url) == nil {
return try realpath(url)
} else {
return url
}
} catch {
//TODO better
print("Bad git repository: \(repo.path.asString)", to: &stderr)
return nil
}
}(self)
/// The set of known versions and their tags.
public lazy var knownVersions: [Version: String] = { repo in
// Get the list of tags.
let out = (try? Git.runPopen([Git.tool, "-C", repo.path.asString, "tag", "-l"])) ?? ""
let tags = out.characters.split(separator: "\n").map{ String($0) }
// First, check if we need to restrict the tag set to version-specific tags.
var knownVersions: [Version: String] = [:]
for versionSpecificKey in Versioning.currentVersionSpecificKeys {
for tag in tags where tag.hasSuffix(versionSpecificKey) {
let specifier = String(tag.characters.dropLast(versionSpecificKey.characters.count))
if let version = Version(specifier) ?? Version.vprefix(specifier) {
knownVersions[version] = tag
}
}
// If we found tags at this version-specific key, we are done.
if !knownVersions.isEmpty {
return knownVersions
}
}
// Otherwise, look for normal tags.
for tag in tags {
if let version = Version(tag) {
knownVersions[version] = tag
}
}
// If we didn't find any versions, look for 'v'-prefixed ones.
//
// FIXME: We should match both styles simultaneously.
if knownVersions.isEmpty {
for tag in tags {
if let version = Version.vprefix(tag) {
knownVersions[version] = tag
}
}
}
return knownVersions
}(self)
/// The set of versions in the repository, in order.
public lazy var versions: [Version] = { repo in
return [Version](repo.knownVersions.keys).sorted()
}(self)
/// Check if repo contains a version tag
public var hasVersion: Bool {
return !versions.isEmpty
}
public var branch: String! {
return try? Git.runPopen([Git.tool, "-C", path.asString, "rev-parse", "--abbrev-ref", "HEAD"]).chomp()
}
public var sha: String! {
return try? Git.runPopen([Git.tool, "-C", path.asString, "rev-parse", "--verify", "HEAD"]).chomp()
}
public func versionSha(tag: String) throws -> String {
return try Git.runPopen([Git.tool, "-C", path.asString, "rev-parse", "--verify", "\(tag)"]).chomp()
}
public var hasLocalChanges: Bool {
let changes = try? Git.runPopen([Git.tool, "-C", path.asString, "status", "--porcelain"]).chomp()
return !(changes?.isEmpty ?? true)
}
public func fetch() throws {
try system(Git.tool, "-C", path.asString, "fetch", "--tags", "origin", environment: ProcessInfo.processInfo.environment, message: nil)
}
}
public class var tool: String {
return getenv("SWIFT_GIT") ?? "git"
}
public class var version: String! {
return try? Git.runPopen([Git.tool, "version"])
}
public class var majorVersionNumber: Int? {
let prefix = "git version"
var version = self.version!
if version.hasPrefix(prefix) {
let prefixRange = version.startIndex...version.index(version.startIndex, offsetBy: prefix.characters.count)
version.removeSubrange(prefixRange)
}
guard let first = version.characters.first else {
return nil
}
return Int(String(first))
}
/// Execute a git command while suppressing output.
//
// FIXME: Move clients of this to using real structured APIs.
public class func runCommandQuietly(_ arguments: [String]) throws {
try system(arguments)
}
/// Execute a git command and capture the output.
//
// FIXME: Move clients of this to using real structured APIs.
public class func runPopen(_ arguments: [String]) throws -> String {
return try popen(arguments)
}
}