Skip to content

Commit

Permalink
Initial
Browse files Browse the repository at this point in the history
  • Loading branch information
tib committed Apr 20, 2020
0 parents commit 860b85e
Show file tree
Hide file tree
Showing 9 changed files with 515 additions and 0 deletions.
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.DS_Store
.build
.swiftpm
Packages
*.xcodeproj
Package.resolved
5 changes: 5 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
FROM swift
WORKDIR /app
COPY . ./
CMD swift package clean
CMD swift test --parallel
17 changes: 17 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004

Copyright (C) 2018-2019 Binary Birds

Authors:

Tibor Bodecs <[email protected]>

Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.

DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

0. You just DO WHAT THE FUCK YOU WANT TO.
19 changes: 19 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// swift-tools-version:5.2
import PackageDescription

let package = Package(
name: "git-kit",
products: [
.library(name: "GitKit", targets: ["GitKit"]),
.library(name: "GitKitDynamic", type: .dynamic, targets: ["GitKit"])
],
dependencies: [
.package(url: "https://github.com/binarybirds/shell-kit", from: "1.0.0"),
],
targets: [
.target(name: "GitKit", dependencies: [
.product(name: "ShellKit", package: "shell-kit"),
]),
.testTarget(name: "GitKitTests", dependencies: ["GitKit"]),
]
)
55 changes: 55 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# GitKit (🐱)

GitKit is a Swift wrapper around the git command line interface.

## Usage

Some basic examples:

```swift
import GitKit

try Git().run(.cmd(.config, "--global user.name"))

let git = Git(path: "~/example/")

try git.run(.cmd(.initialize))
try git.run(.cmd(.status))
try git.run(.cmd(.branch, "-a"))
try git.run(.cmd(.pull))

try git.run(.clone(url: "https://gitlab.com/binarybirds/shell-kit.git"))
try git.run(.commit(message: "some nasty bug fixed"))
try git.run(.log(1))
try git.run(.tag("1.0.0"))
try git.run(.pull(remote: "origin", branch: "master"))
try git.run(.push(remote: "origin", branch: "master"))
try git.run(.create(branch: "dev"))
try git.run(.checkout(branch: "master"))
try git.run(.merge(branch: "dev"))

try git.run(.raw("log -2"))
try git.run(.raw("rebase -i <hash>"))

```

## Install

Just use the Swift Package Manager as usual:

```swift
.package(url: "https://github.com/binarybirds/git-kit", from: "1.0.0"),
```

Don't forget to add "GitKit" to your target as a dependency:

```swift
.product(name: "GitKit", package: "git-kit"),
```

That's it.


## License

[WTFPL](LICENSE) - Do what the fuck you want to.
246 changes: 246 additions & 0 deletions Sources/GitKit/Git.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
/**
Git.swift
GitKit

Created by Tibor Bödecs on 2019.01.02.
Copyright Binary Birds. All rights reserved.
*/

import ShellKit

/// a Git wrapper class
public final class Git: Shell {

/// Git aliases to make the API more convenient
public enum Alias {
case cmd(Command, String? = nil)
case addAll
case commit(message: String, Bool = false)
case clone(url: String)
case checkout(branch: String)
case log(Int? = nil)
case push(remote: String? = nil, branch: String? = nil)
case pull(remote: String? = nil, branch: String? = nil)
case merge(branch: String)
case create(branch: String)
case delete(branch: String)
case tag(String)
case raw(String)

private func commandParams() -> [String] {
var params: [String] = []
switch self {
case .cmd(let command, let args):
params = [command.rawValue]
if let args = args {
params.append(args)
}
case .addAll:
params = [Command.add.rawValue, "."]
case .commit(let message, let allowEmpty):
params = [Command.commit.rawValue, "-m", "\"\(message)\""]
if allowEmpty {
params.append("--allow-empty")
}
case .clone(let url):
params = [Command.clone.rawValue, url]
case .checkout(let branch):
params = [Command.checkout.rawValue, branch]
case .log(let n):
params = [Command.log.rawValue]
if let n = n {
params.append("-\(n)")
}
case .push(let remote, let branch):
params = [Command.push.rawValue]
if let remote = remote {
params.append(remote)
}
if let branch = branch {
params.append(branch)
}
case .pull(let remote, let branch):
params = [Command.pull.rawValue]
if let remote = remote {
params.append(remote)
}
if let branch = branch {
params.append(branch)
}
case .merge(let branch):
params = [Command.merge.rawValue, branch]
case .create(let branch):
params = [Command.checkout.rawValue, "-b", branch]
case .delete(let branch):
params = [Command.branch.rawValue, "-D", branch]
case .tag(let name):
params = [Command.tag.rawValue, name]
case .raw(let command):
params.append(command)
}
return params
}

public var rawValue: String {
self.commandParams().joined(separator: " ")
}
}

/// basic git commands
public enum Command: String {

// MARK: - start a working area (see also: git help tutorial)

case config

case clean
/// Clone a repository into a new directory
case clone
/// Create an empty Git repository or reinitialize an existing one
case initialize = "init"

// MARK: - work on the current change (see also: git help everyday)

/// Add file contents to the index
case add
/// Move or rename a file, a directory, or a symlink
case mv
/// Reset current HEAD to the specified state
case reset
/// Remove files from the working tree and from the index
case rm

// MARK: - examine the history and state (see also: git help revisions)

/// Use binary search to find the commit that introduced a bug
case bisect
/// Print lines matching a pattern
case grep
/// Show commit logs
case log
/// Show various types of objects
case show
/// Show the working tree status
case status

// MARK: - grow, mark and tweak your common history

/// List, create, or delete branches
case branch
/// Switch branches or restore working tree files
case checkout
/// Record changes to the repository
case commit
/// Show changes between commits, commit and working tree, etc
case diff
/// Join two or more development histories together
case merge
/// Reapply commits on top of another base tip
case rebase
/// Create, list, delete or verify a tag object signed with GPG
case tag

// MARK: - collaborate (see also: git help workflows)

/// Download objects and refs from another repository
case fetch
/// Fetch from and integrate with another repository or a local branch
case pull
/// Update remote refs along with associated objects
case push
}

// MARK: - private helper methods

/**
This method helps to assemble a Git command string from an alias

If there is a git repo path (working directory) presented, proper directories
will be used & created recursively if a new repository is being initialized.

- Parameters:
- alias: The git alias to be executed
- args: Additional arguments for the Git alias

- Returns: The Git command
*/
private func rawCommand(_ alias: Alias) -> String {
var cmd: [String] = []
// if there is a path let's change directory first
if let path = self.path {
// try to create work dir at given path for init or clone commands
if
alias.rawValue.hasPrefix(Command.initialize.rawValue) ||
alias.rawValue.hasPrefix(Command.clone.rawValue)
{
cmd += ["mkdir", "-p", path, "&&"]
}
cmd += ["cd", path, "&&"]
}
cmd += ["git", alias.rawValue]

let command = cmd.joined(separator: " ")

if self.verbose {
print(command)
}
return command
}

// MARK: - public api

/// work directory, if peresent a directory change will occur before running any Git commands
///
/// NOTE: if the git init command is called with a non-existing path, directories
/// presented in the path string will be created recursively
public var path: String?

// prints git commands constructed from the alias before execution
public var verbose = false

/**
Initializes a new Git object

- Parameters:
- path: The path of the Swift package (work directory)
- type: The type of the shell, default: /bin/sh
- env: Additional environment variables for the shell, default: empty

*/
public init(path: String? = nil, type: String = "/bin/sh", env: [String: String] = [:]) {
self.path = path

super.init(type, env: env)
}

/**
Runs a specific Git alias through the current shell.

- Parameters:
- alias: The git command alias to be executed

- Throws:
`ShellError.outputData` if the command execution succeeded but the output is empty,
otherwise `ShellError.generic(Int, String)` where the first parameter is the exit code,
the second is the error message

- Returns: The output string of the command without trailing newlines
*/
@discardableResult
public func run(_ alias: Alias) throws -> String {
try self.run(self.rawCommand(alias))
}

/**
Async version of the run function

- Parameters:
- alias: The git command alias to be executed
- completion: The completion block with the output and error

The command will be executed on a concurrent dispatch queue.
*/
public func run(_ alias: Alias, completion: @escaping ((String?, Swift.Error?) -> Void)) {
self.run(self.rawCommand(alias), completion: completion)
}
}
Loading

0 comments on commit 860b85e

Please sign in to comment.