From 558fe1238a9ff3109fdb0b5346435e3eeb4a4e2b Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Thu, 3 Jul 2025 15:31:37 -0800 Subject: [PATCH 1/8] add parameter to allow specifying a remote tracking branch --- Sources/GitKit/Git.swift | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Sources/GitKit/Git.swift b/Sources/GitKit/Git.swift index e68edb1..56263c3 100644 --- a/Sources/GitKit/Git.swift +++ b/Sources/GitKit/Git.swift @@ -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) @@ -68,12 +73,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 { From 4ec4ee2c2304d2ed21f3e94c8e269d843158fd8d Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Thu, 3 Jul 2025 15:32:39 -0800 Subject: [PATCH 2/8] fix params; abbrev-ref is a boolean option; add separate param for the actual revision to parse --- Sources/GitKit/Git.swift | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/Sources/GitKit/Git.swift b/Sources/GitKit/Git.swift index 56263c3..6f8cba8 100644 --- a/Sources/GitKit/Git.swift +++ b/Sources/GitKit/Git.swift @@ -38,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) @@ -154,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 { From 26e99e6ea4025556fedb645b3296298cbc01fd65 Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Thu, 3 Jul 2025 15:31:37 -0800 Subject: [PATCH 3/8] add parameter to allow specifying a remote tracking branch --- Sources/GitKit/Git.swift | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Sources/GitKit/Git.swift b/Sources/GitKit/Git.swift index e68edb1..56263c3 100644 --- a/Sources/GitKit/Git.swift +++ b/Sources/GitKit/Git.swift @@ -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) @@ -68,12 +73,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 { From 05f3b33bd561a16cf01131206e9967fae11e3f35 Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Sat, 19 Jul 2025 19:03:14 -0800 Subject: [PATCH 4/8] add test --- Tests/GitKitTests/GitKitTests.swift | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Tests/GitKitTests/GitKitTests.swift b/Tests/GitKitTests/GitKitTests.swift index 685998f..e520643 100644 --- a/Tests/GitKitTests/GitKitTests.swift +++ b/Tests/GitKitTests/GitKitTests.swift @@ -25,6 +25,7 @@ final class GitKitTests: XCTestCase { ("testLog", testLog), ("testCommandWithArgs", testCommandWithArgs), ("testClone", testClone), + ("testCheckoutRemoteTracking", testCheckoutRemoteTracking), ] // MARK: - helpers @@ -123,6 +124,25 @@ final class GitKitTests: XCTestCase { 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") + } + #if os(macOS) func testAsyncRun() throws { let path = self.currentPath() From dd7f2aa7139f52431de122193449ece8db1dc79d Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Thu, 3 Jul 2025 15:31:37 -0800 Subject: [PATCH 5/8] add parameter to allow specifying a remote tracking branch --- Sources/GitKit/Git.swift | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Sources/GitKit/Git.swift b/Sources/GitKit/Git.swift index 04ea514..5255778 100644 --- a/Sources/GitKit/Git.swift +++ b/Sources/GitKit/Git.swift @@ -19,7 +19,12 @@ public final class Git: Shell { case commit(message: String, Bool = false) case config(name: String, value: String) case clone(url: String) - 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) @@ -59,12 +64,15 @@ public final class Git: Shell { } case .clone(let url): params = [Command.clone.rawValue, url] - 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 { From 1ca825219018d03dfa554b919011e5ffcd578f1e Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Thu, 3 Jul 2025 15:32:39 -0800 Subject: [PATCH 6/8] fix params; abbrev-ref is a boolean option; add separate param for the actual revision to parse --- Sources/GitKit/Git.swift | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Sources/GitKit/Git.swift b/Sources/GitKit/Git.swift index 5255778..944d8e6 100644 --- a/Sources/GitKit/Git.swift +++ b/Sources/GitKit/Git.swift @@ -37,7 +37,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) @@ -145,8 +149,6 @@ public final class Git: Shell { params.append(command) case .config(name: let name, value: let value): params = [Command.config.rawValue, "--add", name, value] - case .revParse(abbrevRef: let abbrevRef): - params = [Command.revParse.rawValue, "--abbrev-ref", abbrevRef] case .revList(let branch, let count, let revisions): params = [Command.revList.rawValue] if count { From 9232d258b891dbb069ab0366fdb5fbc62f70eeba Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Sat, 19 Jul 2025 19:22:02 -0800 Subject: [PATCH 7/8] add test --- Sources/GitKit/Git.swift | 6 +++++ Tests/GitKitTests/GitKitTests.swift | 40 +++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/Sources/GitKit/Git.swift b/Sources/GitKit/Git.swift index 6f8cba8..cf51ff2 100644 --- a/Sources/GitKit/Git.swift +++ b/Sources/GitKit/Git.swift @@ -174,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 { diff --git a/Tests/GitKitTests/GitKitTests.swift b/Tests/GitKitTests/GitKitTests.swift index e520643..a209f27 100644 --- a/Tests/GitKitTests/GitKitTests.swift +++ b/Tests/GitKitTests/GitKitTests.swift @@ -26,6 +26,7 @@ final class GitKitTests: XCTestCase { ("testCommandWithArgs", testCommandWithArgs), ("testClone", testClone), ("testCheckoutRemoteTracking", testCheckoutRemoteTracking), + ("testRevParse", testRevParse), ] // MARK: - helpers @@ -117,6 +118,14 @@ 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", 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") @@ -143,6 +152,37 @@ final class GitKitTests: XCTestCase { 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", 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() From b101640d0f56a34f7d8b353e7fca6216c267767a Mon Sep 17 00:00:00 2001 From: Andrew McKnight Date: Sat, 19 Jul 2025 19:25:08 -0800 Subject: [PATCH 8/8] fix test build --- Tests/GitKitTests/GitKitTests.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/GitKitTests/GitKitTests.swift b/Tests/GitKitTests/GitKitTests.swift index a209f27..b554ebf 100644 --- a/Tests/GitKitTests/GitKitTests.swift +++ b/Tests/GitKitTests/GitKitTests.swift @@ -121,8 +121,8 @@ final class GitKitTests: XCTestCase { // Initialize a repository and make an initial commit try git.run(.raw("init")) - try git.run(.commit(message: "initial commit", true)) - + 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") @@ -160,7 +160,7 @@ final class GitKitTests: XCTestCase { // Initialize a repository and make an initial commit try git.run(.raw("init")) - try git.run(.commit(message: "initial commit", true)) + 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"))