• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

JuliaLang / julia / #37535

pending completion
#37535

push

local

web-flow
irinterp: Fix accidentally introduced deletion of effectful statement (#49750)

I moved around some code in #49692 that broadened the replacement of
statements by their const results. This is fine for how we're currently
using irinterp in base, because we're requiring some fairly strong
effects, but some downstream pipelines (and potentially Base in the future)
want to use irinterp on code with arbitrary effects, so put in an
appropriate check.

2 of 2 new or added lines in 2 files covered. (100.0%)

71489 of 83224 relevant lines covered (85.9%)

32529585.83 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/stdlib/LibGit2/src/gitcredential.jl
1
# This file is a part of Julia. License is MIT: https://julialang.org/license
2

3
const GIT_CRED_ATTRIBUTES = ("protocol", "host", "path", "username", "password", "url")
4

5
"""
6
    GitCredential
7

8
Git credential information used in communication with git credential helpers. The field are
9
named using the [input/output key specification](https://git-scm.com/docs/git-credential#IOFMT).
10
"""
11
mutable struct GitCredential
12
    protocol::Union{String, Nothing}
13
    host::Union{String, Nothing}
14
    path::Union{String, Nothing}
15
    username::Union{String, Nothing}
16
    password::Union{Base.SecretBuffer, Nothing}
17
    use_http_path::Bool
18

19
    function GitCredential(
×
20
            protocol::Union{AbstractString, Nothing}=nothing,
21
            host::Union{AbstractString, Nothing}=nothing,
22
            path::Union{AbstractString, Nothing}=nothing,
23
            username::Union{AbstractString, Nothing}=nothing,
24
            password::Union{AbstractString, Nothing}=nothing)
25
        new(protocol, host, path, username, password, true)
×
26
    end
27
end
28

29
function GitCredential(cfg::GitConfig, url::AbstractString)
×
30
    fill!(cfg, parse(GitCredential, url))
×
31
end
32

33
function GitCredential(user_pass_cred::UserPasswordCredential, url::AbstractString)
×
34
    cred = parse(GitCredential, url)
×
35
    cred.username = user_pass_cred.user
×
36
    cred.password = deepcopy(user_pass_cred.pass)
×
37
    return cred
×
38
end
39

40
Base.:(==)(c1::GitCredential, c2::GitCredential) = (c1.protocol, c1.host, c1.path, c1.username, c1.password, c1.use_http_path) ==
×
41
                                                   (c2.protocol, c2.host, c2.path, c2.username, c2.password, c2.use_http_path)
42
Base.hash(cred::GitCredential, h::UInt) = hash(GitCredential, hash((cred.protocol, cred.host, cred.path, cred.username, cred.password, cred.use_http_path), h))
×
43

44
function Base.shred!(cred::GitCredential)
×
45
    cred.protocol = nothing
×
46
    cred.host = nothing
×
47
    cred.path = nothing
×
48
    cred.username = nothing
×
49
    pwd = cred.password
×
50
    pwd !== nothing && Base.shred!(pwd)
×
51
    cred.password = nothing
×
52
    return cred
×
53
end
54

55

56
"""
57
    ismatch(url, git_cred) -> Bool
58

59
Checks if the `git_cred` is valid for the given `url`.
60
"""
61
function ismatch(url::AbstractString, git_cred::GitCredential)
×
62
    isempty(url) && return true
×
63

64
    m = match(URL_REGEX, url)
×
65
    m === nothing && error("Unable to parse URL")
×
66

67
    # Note: missing URL groups match anything
68
    (m[:scheme] === nothing ? true : m[:scheme] == git_cred.protocol) &&
×
69
    (m[:host] === nothing ? true : m[:host] == git_cred.host) &&
70
    (m[:path] === nothing ? true : m[:path] == git_cred.path) &&
71
    (m[:user] === nothing ? true : m[:user] == git_cred.username)
72
end
73

74
function isfilled(cred::GitCredential)
×
75
    cred.username !== nothing && cred.password !== nothing
×
76
end
77

78
function Base.parse(::Type{GitCredential}, url::AbstractString)
×
79
    m = match(URL_REGEX, url)
×
80
    m === nothing && error("Unable to parse URL")
×
81
    return GitCredential(
×
82
        m[:scheme],
83
        m[:host],
84
        m[:path],
85
        m[:user],
86
        m[:password],
87
    )
88
end
89

90
function Base.copy!(a::GitCredential, b::GitCredential)
×
91
    Base.shred!(a)
×
92
    a.protocol = b.protocol
×
93
    a.host = b.host
×
94
    a.path = b.path
×
95
    a.username = b.username
×
96
    a.password = b.password === nothing ? nothing : copy(b.password)
×
97
    return a
×
98
end
99

100
function Base.write(io::IO, cred::GitCredential)
×
101
    cred.protocol !== nothing && write(io, "protocol=", cred.protocol, '\n')
×
102
    cred.host !== nothing && write(io, "host=", cred.host, '\n')
×
103
    cred.path !== nothing && cred.use_http_path && write(io, "path=", cred.path, '\n')
×
104
    cred.username !== nothing && write(io, "username=", cred.username, '\n')
×
105
    cred.password !== nothing && write(io, "password=", cred.password, '\n')
×
106
    nothing
×
107
end
108

109
function Base.read!(io::IO, cred::GitCredential)
×
110
    # https://git-scm.com/docs/git-credential#IOFMT
111
    while !(eof(io)::Bool)
×
112
        key::AbstractString = readuntil(io, '=')
×
113
        if key == "password"
×
114
            value = Base.SecretBuffer()
×
115
            while !(eof(io)::Bool) && (c = read(io, UInt8)) != UInt8('\n')
×
116
                write(value, c)
×
117
            end
×
118
            seekstart(value)
×
119
        else
120
            value = readuntil(io, '\n')
×
121
        end
122

123
        if key == "url"
×
124
            # Any components which are missing from the URL will be set to empty
125
            # https://git-scm.com/docs/git-credential#git-credential-codeurlcode
126
            Base.shred!(parse(GitCredential, value::AbstractString)) do urlcred
×
127
                copy!(cred, urlcred)
×
128
            end
129
        elseif key in GIT_CRED_ATTRIBUTES
×
130
            field = getproperty(cred, Symbol(key))
×
131
            field !== nothing && Symbol(key) === :password && Base.shred!(field)
×
132
            setproperty!(cred, Symbol(key), value)
×
133
        elseif !all(isspace, key)
×
134
            @warn "Unknown git credential attribute found: $(repr(key))"
×
135
        end
136
    end
×
137

138
    return cred
×
139
end
140

141
function fill!(cfg::GitConfig, cred::GitCredential)
×
142
    cred.use_http_path = use_http_path(cfg, cred)
×
143

144
    # When the username is missing default to using the username set in the configuration
145
    if cred.username === nothing
×
146
        cred.username = default_username(cfg, cred)
×
147
    end
148

149
    for helper in credential_helpers(cfg, cred)
×
150
        fill!(helper, cred)
×
151

152
        # "Once Git has acquired both a username and a password, no more helpers will be
153
        # tried." – https://git-scm.com/docs/gitcredentials#gitcredentials-helper
154
        !isfilled(cred) && break
×
155
    end
×
156

157
    return cred
×
158
end
159

160
struct GitCredentialHelper
161
    cmd::Cmd
×
162
end
163

164
function Base.parse(::Type{GitCredentialHelper}, helper::AbstractString)
×
165
    # The helper string can take on different behaviors depending on the value:
166
    # - "Code after `!` evaluated in shell" – https://git-scm.com/book/en/v2/Git-Tools-Credential-Storage
167
    # - "If the helper name is not an absolute path, then the string `git credential-` is
168
    #   prepended." – https://git-scm.com/docs/gitcredentials#gitcredentials-helper
169
    if startswith(helper, '!')
×
170
        cmd_str = helper[2:end]
×
171
    elseif isabspath(first(Base.shell_split(helper)))
×
172
        cmd_str = helper
×
173
    else
174
        cmd_str = "git credential-$helper"
×
175
    end
176

177
    GitCredentialHelper(`$(Base.shell_split(cmd_str))`)
×
178
end
179

180
function Base.:(==)(a::GitCredentialHelper, b::GitCredentialHelper)
×
181
    a.cmd == b.cmd
×
182
end
183

184
function run!(helper::GitCredentialHelper, operation::AbstractString, cred::GitCredential)
×
185
    cmd = `$(helper.cmd) $operation`
×
186
    p = open(cmd, "r+")
×
187

188
    # Provide the helper with the credential information we know
189
    write(p, cred)
×
190
    write(p, "\n")
×
191
    t = @async close(p.in)
×
192

193
    # Process the response from the helper
194
    Base.read!(p, cred)
×
195
    wait(p)
×
196

197
    return cred
×
198
end
199

200
function run(helper::GitCredentialHelper, operation::AbstractString, cred::GitCredential)
×
201
    run!(helper, operation, deepcopy(cred))
×
202
end
203

204
# The available actions between using `git credential` and helpers are slightly different.
205
# We will directly interact with the helpers as that way we can request credential
206
# information without a prompt (helper `get` vs. git credential `fill`).
207
# https://git-scm.com/book/en/v2/Git-Tools-Credential-Storage
208

209
fill!(helper::GitCredentialHelper, cred::GitCredential) = run!(helper, "get", cred)
×
210
approve(helper::GitCredentialHelper, cred::GitCredential) = run(helper, "store", cred)
×
211
reject(helper::GitCredentialHelper, cred::GitCredential) = run(helper, "erase", cred)
×
212

213
"""
214
    credential_helpers(config, git_cred) -> Vector{GitCredentialHelper}
215

216
Return all of the `GitCredentialHelper`s found within the provided `config` which are valid
217
for the specified `git_cred`.
218
"""
219
function credential_helpers(cfg::GitConfig, cred::GitCredential)
×
220
    helpers = GitCredentialHelper[]
×
221

222
    # https://git-scm.com/docs/gitcredentials#gitcredentials-helper
223
    for entry in GitConfigIter(cfg, r"credential.*\.helper$")
×
224
        section, url, name, value = split_cfg_entry(entry)
×
225
        @assert name == "helper"
×
226

227
        # Only use configuration settings where the URL applies to the git credential
228
        ismatch(url, cred) || continue
×
229

230
        # An empty credential.helper resets the list to empty
231
        if isempty(value)
×
232
            empty!(helpers)
×
233
        else
234
            Base.push!(helpers, parse(GitCredentialHelper, value))
×
235
        end
236
    end
×
237

238
    return helpers
×
239
end
240

241
"""
242
    default_username(config, git_cred) -> Union{String, Nothing}
243

244
Return the default username, if any, provided by the `config` which is valid for the
245
specified `git_cred`.
246
"""
247
function default_username(cfg::GitConfig, cred::GitCredential)
×
248
    # https://git-scm.com/docs/gitcredentials#gitcredentials-username
249
    for entry in GitConfigIter(cfg, r"credential.*\.username")
×
250
        section, url, name, value = split_cfg_entry(entry)
×
251
        @assert name == "username"
×
252

253
        # Only use configuration settings where the URL applies to the git credential
254
        ismatch(url, cred) || continue
×
255
        return value
×
256
    end
×
257

258
    return nothing
×
259
end
260

261
function use_http_path(cfg::GitConfig, cred::GitCredential)
×
262
    seen_specific = false
×
263
    use_path = false  # Default is to ignore the path
×
264

265
    # https://git-scm.com/docs/gitcredentials#gitcredentials-useHttpPath
266
    #
267
    # Note: Ideally the regular expression should use "useHttpPath"
268
    # https://github.com/libgit2/libgit2/issues/4390
269
    for entry in GitConfigIter(cfg, r"credential.*\.usehttppath")
×
270
        section, url, name, value = split_cfg_entry(entry)
×
271

272
        # Ignore global configuration if we have already encountered more specific entry
273
        if ismatch(url, cred) && (!isempty(url) || !seen_specific)
×
274
            seen_specific = !isempty(url)
×
275
            use_path = value == "true"
×
276
        end
277
    end
×
278

279
    return use_path
×
280
end
281

282
approve(cfg::GitConfig, cred::AbstractCredential, url::AbstractString) = nothing
×
283
reject(cfg::GitConfig, cred::AbstractCredential, url::AbstractString) = nothing
×
284

285
function approve(cfg::GitConfig, cred::UserPasswordCredential, url::AbstractString)
×
286
    git_cred = GitCredential(cred, url)
×
287
    git_cred.use_http_path = use_http_path(cfg, git_cred)
×
288

289
    for helper in credential_helpers(cfg, git_cred)
×
290
        approve(helper, git_cred)
×
291
    end
×
292

293
    Base.shred!(git_cred)
×
294
    nothing
×
295
end
296

297
function reject(cfg::GitConfig, cred::UserPasswordCredential, url::AbstractString)
×
298
    git_cred = GitCredential(cred, url)
×
299
    git_cred.use_http_path = use_http_path(cfg, git_cred)
×
300

301
    for helper in credential_helpers(cfg, git_cred)
×
302
        reject(helper, git_cred)
×
303
    end
×
304

305
    Base.shred!(git_cred)
×
306
    nothing
×
307
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc