Skip to content
32 changes: 27 additions & 5 deletions Sources/GitKit/Git.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,12 @@ public final class Git: Shell {
case writeConfig(name: String, value: String)
case readConfig(name: String)
case clone(url: String , dirName: String? = nil)
case checkout(branch: String, create: Bool = false)

/// - parameter branch the name of the branch to checkout
/// - parameter create whether to create a new branch or checkout an existing one
/// - parameter tracking when creating a new branch, the name of the remote branch it should track
case checkout(branch: String, create: Bool = false, tracking: String? = nil)

case log(numberOfCommits: Int? = nil, options: [String]? = nil, revisions: String? = nil)
case push(remote: String? = nil, branch: String? = nil)
case pull(remote: String? = nil, branch: String? = nil, rebase: Bool = false)
Expand All @@ -33,7 +38,11 @@ public final class Git: Shell {
case submoduleForeach(recursive: Bool = false, command: String)
case renameRemote(oldName: String, newName: String)
case addRemote(name: String, url: String)
case revParse(abbrevRef: String)

/// - parameter abbrevRef whether or not the result should be the abbreviated reference name or the full commit SHA hash
/// - parameter revision the name of the revision to parse. can be symbolic (`@`), human-readable (`origin/HEAD`) or a commit SHA hash
case revParse(abbrevRef: Bool, revision: String)

case revList(branch: String, count: Bool = false, revisions: String? = nil)
case raw(String)
case lsRemote(url: String, limitToHeads: Bool = false)
Expand Down Expand Up @@ -68,12 +77,15 @@ public final class Git: Shell {
if let dirName = dirname {
params.append(dirName)
}
case .checkout(let branch, let create):
case .checkout(let branch, let create, let tracking):
params = [Command.checkout.rawValue]
if create {
params.append("-b")
}
params.append(branch)
if let tracking {
params.append(tracking)
}
case .log(let numberOfCommits, let options, let revisions):
params = [Command.log.rawValue]
if let numberOfCommits = numberOfCommits {
Expand Down Expand Up @@ -146,10 +158,14 @@ public final class Git: Shell {
params.append(command)
case .writeConfig(let name, let value):
params = [Command.config.rawValue, "--add", name, value]
case .revParse(abbrevRef: let abbrevRef):
params = [Command.revParse.rawValue, "--abbrev-ref", abbrevRef]
case .readConfig(let name):
params = [Command.config.rawValue, "--get", name]
case .revParse(let abbrevRef, let revision):
params = [Command.revParse.rawValue]
if abbrevRef {
params.append("--abbrev-ref")
}
params.append(revision)
case .revList(let branch, let count, let revisions):
params = [Command.revList.rawValue]
if count {
Expand All @@ -158,6 +174,12 @@ public final class Git: Shell {
if let revisions = revisions {
params.append(revisions)
}
case .revParse(let abbrevRef, let revision):
params = [Command.revParse.rawValue]
if abbrevRef {
params.append("--abbrev-ref")
}
params.append(revision)
case .lsRemote(url: let url, limitToHeads: let limitToHeads):
params = [Command.lsRemote.rawValue]
if limitToHeads {
Expand Down
60 changes: 60 additions & 0 deletions Tests/GitKitTests/GitKitTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ final class GitKitTests: XCTestCase {
("testLog", testLog),
("testCommandWithArgs", testCommandWithArgs),
("testClone", testClone),
("testCheckoutRemoteTracking", testCheckoutRemoteTracking),
("testRevParse", testRevParse),
]

// MARK: - helpers
Expand Down Expand Up @@ -116,13 +118,71 @@ final class GitKitTests: XCTestCase {

try self.clean(path: path)
let git = Git(path: path)

// Initialize a repository and make an initial commit
try git.run(.raw("init"))
try git.run(.commit(message: "initial commit", allowEmpty: true))

// Test 1: Get abbreviated reference name for HEAD
let abbrevRef = try git.run(.revParse(abbrevRef: true, revision: "HEAD"))
XCTAssertEqual(abbrevRef, "main", "Should return abbreviated reference name")

try git.run(.clone(url: "https://github.com/binarybirds/shell-kit.git", dirName: "MyCustomDirectory"))
let statusOutput = try git.run("cd \(path)/MyCustomDirectory && git status")
try self.clean(path: path)
self.assert(type: "output", result: statusOutput, expected: expectation)
}

func testCheckoutRemoteTracking() throws {
let path = self.currentPath()

try self.clean(path: path)
let git = Git(path: path)

try git.run(.clone(url: "https://github.com/binarybirds/shell-kit.git"))

let repoPath = "\(path)/shell-kit"
let repoGit = Git(path: repoPath)

try repoGit.run(.checkout(branch: "feature-branch", create: true, tracking: "origin/main"))
let branchOutput = try repoGit.run(.raw("branch -vv"))
try self.clean(path: path)

XCTAssertTrue(branchOutput.contains("feature-branch"), "New branch should be created")
XCTAssertTrue(branchOutput.contains("origin/main"), "Branch should track origin/main")
}

func testRevParse() throws {
let path = self.currentPath()

try self.clean(path: path)
let git = Git(path: path)

// Initialize a repository and make an initial commit
try git.run(.raw("init"))
try git.run(.commit(message: "initial commit", allowEmpty: true))

// Test 1: Get abbreviated reference name for HEAD
let abbrevRef = try git.run(.revParse(abbrevRef: true, revision: "HEAD"))
XCTAssertEqual(abbrevRef, "main", "Should return abbreviated reference name")

// Test 2: Get full commit SHA for HEAD
let fullSHA = try git.run(.revParse(abbrevRef: false, revision: "HEAD"))
XCTAssertTrue(fullSHA.count == 40, "Should return full 40-character SHA")
XCTAssertTrue(fullSHA.allSatisfy { $0.isHexDigit }, "SHA should contain only hex characters")

// Test 3: Parse symbolic reference (@)
let symbolicRef = try git.run(.revParse(abbrevRef: false, revision: "@"))
XCTAssertEqual(symbolicRef, fullSHA, "Symbolic '@' should resolve to same SHA as HEAD")

// Test 4: Get abbreviated reference for current branch
let currentBranch = try git.run(.revParse(abbrevRef: true, revision: "@"))
XCTAssertEqual(currentBranch, "main", "Should return current branch name")

// Clean up
try self.clean(path: path)
}

#if os(macOS)
func testAsyncRun() throws {
let path = self.currentPath()
Expand Down