Git credential helper using Swift
What’s git credential helper
Git needs a way to know the credentials if we use it to access the repos that are not public. If using repo’s ssh address, configuring ssh key is enough. For repos address that are in https url format, username and password are needed, where password can be personal access token or OAuth access token depending on the server configuration.
When needed, git prompts to user to input this piece of information on the terminal. However, user interactions are not always feasible in situations like running it in a CI/CD environment. Also, it can be very repetitive typing in username and password in daily work.
In order to automate this process, we can put them into gitconfig file like below:
[credential "https://somereposerver.com"]
username = name
password = $PASSWORD # Assuming can read password from environment variable PASSWORD
When it comes more complicated password logic, you might find it challenging to use the above way. That’s where git credential helper script comes in. We can implement the password logic needed in our own script and configure git to call that script when needed.
What are the best languages to develop it
In theory, any languages can be used in order to develop a git credential helper as long as we can develop an executable command using them. However, in this scenario, languages like shell script, python and ruby etc. might be used more often as it’s convenient to just use SHEBANG to run a text file directly.
You might have already known Swift can do scripting too! In this article, we are going to use Swift as the audience of this channel are mainly mobile developers.
For the quick reference, we need to add SHEBANG directive at the top of the Swift file like below:
#!/usr/bin/swift
print("Swift scripting")
And we also need to run $ chmod +x file.swift to make it work. Of course, this can only possible on computers that have Swift installed. As Apple developers, this should not be a big issue :)
Configure git to user the helper
Before configuring any new helpers, we can run the following command to see what helper we have currently.
git config - get-all credential.helper
To configure a new helper, use the following command:
git config --global credential.helper the_path_to_the_helper.swift
Depending on if you want to make the helper globally available or not, we can choose having or ignore --global . Also if want to have a more tailored way of configuring the helper, for example, to limit this helper only for certain urls, please refer to the git document here.
Design and implementation
The three operations that git interacts with the helper script are the following:
- Get: request credentials from the helper
- Store: ask helper to store the credentials
- Erase: ask help to delete the credentials
The helper script can implement all of them or just some that are of interest. For each operation, the git will provide information through standard input in the format of the following(refer here for more details ).
protocol=https
host=someserver.com
In Swift, we can easily read them using the following code:
while let line = readLine() {
// The content of line would be \`protocol=https\` or \`host=someserver.com\` etc.
}
Also we need to use CommandLine.arguments to parse the operation type. Please refer to the following code:
let operation = CommandLine.arguments.last
switch operation {
case "get": get()
case "store": store()
case "erase": erase()
default: break
}
With this, we can implement the logic we need like the following:
#!/usr/bin/swift
import Foundation
let name = "someusername"
let name1 = "someusername1"
let password = "somepassword"
let password1 = "somepassword1"
let operation = CommandLine.arguments.last
switch operation {
case "get": get()
case "store": store()
case "erase": erase()
default: break
}
func get() {
while let line = readLine() {
if line.starts(with: "host") {
let server = line.components(separatedBy: "=").last ?? ""
switch server {
case "server1.com":
// Output username and password for server1.com to git
print("username=\\(name1)\\npassword=\\(password1)")
exit(0)
default:
// Output username and password for other servers
print("username=\\(name)\\npassword=\\(password)")
exit(0)
}
}
}
}
func store() { /* Ignore for simplicity */ }
func erase() { /* Ignore for simplicity */ }
Other Helpful information
Launch the helper script alone
In the development process, we can just launch the script alone with the expected operation types in terminal to test it like below:
./helper.swift get
Using Environment variables in Swift
If we need to read information like password from environment variables, use the following syntax:
let password = ProcessInfo.processInfo.environment["PASSWORD"]
useHttpPath
By default, git only provide protocol and host information for get operation. If the credential logic depends on the url path, we need to tell git to provide it, for which, using the following git command:
git config --global credential.useHttpPath true
GIT_TRACE
After configuring our helper script, we can trigger it using various git command like git clone, git push etc. If you want to verify how the help function triggered, we can turn on git trace functionality. The easiest way to do it is to prefix with the GIT_TRACE environment value to the git command like the following:
GIT_TRACE=1 git clone url_to_the_repo
This will output all the commands that git triggers like below, from which we can find our helper function gets called in one of therun-command s.
However, we cannot see the credentials from our helper script. For debugging purpose, if we need to see the output of our helper script, we need to output that content to standard error output. One way to achieve it in Swift is using the following syntax:
let errorMessage = "Some error message"
let errorOutput = FileHandle.standardError
if let messageData = errorMessage.data(using: .utf8) {
errorOutput.write(messageData)
}
Conclusion
Git credential helper can be super helpful when work around with remote repo permission issues. And, as a Swift developers, we can leverage Swift’s scripting capability to develop one. By using the language that we are familiar with, this task can be done with ease.
Sharing is caring!