Skip to content

Commit 9132080

Browse files
committed
Feature: Added all required Git commands and finished those that hasn't been completed yet
1 parent 6224857 commit 9132080

File tree

212 files changed

+13126
-4319
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

212 files changed

+13126
-4319
lines changed

.swiftpm/xcode/xcshareddata/xcschemes/Version-Control.xcscheme

Lines changed: 0 additions & 66 deletions
This file was deleted.

Package.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ let package = Package(
2121
// Targets can depend on other targets in this package, and on products in packages this package depends on.
2222
.target(
2323
name: "Version-Control",
24-
dependencies: [])
24+
dependencies: []),
25+
.testTarget(
26+
name: "Version-Control-Test",
27+
dependencies: [
28+
"Version-Control"
29+
]
30+
),
2531
]
2632
)
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
//
2+
// GitHubActions.swift
3+
//
4+
//
5+
// Created by Tihan-Nico Paxton on 2023/09/25.
6+
//
7+
8+
import Foundation
9+
import AppKit
10+
11+
public enum GitHubViewType: String {
12+
case tree = "tree"
13+
case compare = "compare"
14+
}
15+
16+
public struct GitHubActions {
17+
18+
internal func getBranchName(directoryURL: URL) throws -> String {
19+
return try Branch().getCurrentBranch(directoryURL: directoryURL)
20+
}
21+
22+
internal func getCurrentRepositoryGitHubURL(directoryURL: URL) throws -> String {
23+
let remoteUrls: [GitRemote] = try Remote().getRemotes(directoryURL: directoryURL)
24+
25+
for remote in remoteUrls {
26+
if remote.url.contains("github.com") {
27+
return remote.url
28+
}
29+
}
30+
return ""
31+
}
32+
33+
/// Open a specific branch of a GitHub repository in a web browser.
34+
///
35+
/// This function constructs the URL for a specific branch of a GitHub repository based on the provided parameters and opens it in the default web browser.
36+
///
37+
/// - Parameters:
38+
/// - viewType: The type of view to open on GitHub (e.g., code, commits, pulls).
39+
/// - directoryURL: The local directory URL of the Git repository.
40+
///
41+
/// - Throws: An error if there's an issue with constructing the URL or opening it in the web browser.
42+
///
43+
/// - Example:
44+
/// ```swift
45+
/// do {
46+
/// let viewType = GitHubViewType.commits
47+
/// let directoryURL = URL(fileURLWithPath: "/path/to/local/repository")
48+
/// try openBranchOnGitHub(viewType: viewType, directoryURL: directoryURL)
49+
/// } catch {
50+
/// print("Error: Unable to open the GitHub branch.")
51+
/// }
52+
public func openBranchOnGitHub(viewType: GitHubViewType,
53+
directoryURL: URL) throws {
54+
let htmlURL = try getCurrentRepositoryGitHubURL(directoryURL: directoryURL)
55+
let branchName = try getBranchName(directoryURL: directoryURL)
56+
57+
let urlEncodedBranchName = branchName.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed)
58+
59+
guard let encodedBranchName = urlEncodedBranchName else {
60+
return
61+
}
62+
63+
let url = URL(string: "\(htmlURL)/\(viewType)/\(encodedBranchName)")
64+
65+
NSWorkspace.shared.open(url!)
66+
}
67+
68+
/// Open the GitHub issue creation page for the current repository in a web browser.
69+
///
70+
/// This function constructs the URL for creating a new issue in the current repository on GitHub and opens it in the default web browser.
71+
///
72+
/// - Parameter directoryURL: The local directory URL of the Git repository.
73+
///
74+
/// - Throws: An error if there's an issue with constructing the URL or opening it in the web browser.
75+
///
76+
/// - Example:
77+
/// ```swift
78+
/// do {
79+
/// let directoryURL = URL(fileURLWithPath: "/path/to/local/repository")
80+
/// try openIssueCreationOnGitHub(directoryURL: directoryURL)
81+
/// } catch {
82+
/// print("Error: Unable to open the GitHub issue creation page.")
83+
/// }
84+
public func openIssueCreationOnGitHub(directoryURL: URL) throws {
85+
let repositoryURL = try getCurrentRepositoryGitHubURL(directoryURL: directoryURL)
86+
87+
let url = URL(string: "\(repositoryURL)/issues/new/choose")
88+
89+
NSWorkspace.shared.open(url!)
90+
}
91+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
//
2+
// Add.swift
3+
// AuroraEditor
4+
//
5+
// Created by Nanashi Li on 2022/08/12.
6+
// Copyright © 2022 Aurora Company. All rights reserved.
7+
// This source code is restricted for Aurora Editor usage only.
8+
//
9+
10+
import Foundation
11+
12+
public struct Add {
13+
/// Stages a file that has conflicts after a Git operation such as a merge or cherry-pick.
14+
///
15+
/// This function is typically used to mark a file with conflicts as resolved by adding it to the staging area.
16+
/// After resolving the conflicts manually in the file, you would call this function to stage the file.
17+
///
18+
/// - Parameters:
19+
/// - directoryURL: The URL of the directory where the Git repository is located.
20+
/// - file: A `WorkingDirectoryFileChange` object representing the file with conflicts to be staged.
21+
/// - Throws: An error if the `git add` command fails.
22+
func addConflictedFile(directoryURL: URL,
23+
file: WorkingDirectoryFileChange) throws {
24+
25+
try GitShell().git(args: ["add", "--", file.path],
26+
path: directoryURL,
27+
name: #function)
28+
}
29+
}
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
//
2+
// Apply.swift
3+
// AuroraEditor
4+
//
5+
// Created by Nanashi Li on 2022/08/15.
6+
// Copyright © 2022 Aurora Company. All rights reserved.
7+
// This source code is restricted for Aurora Editor usage only.
8+
//
9+
10+
import Foundation
11+
12+
public struct Apply {
13+
14+
/// Applies changes to the index for a specified file in a git repository, with special handling for renamed files.
15+
///
16+
/// If the file change represents a rename, the function reconstructs this rename in the index. This involves
17+
/// staging the old file path for update and determining the blob ID of the removed file to update the index.
18+
/// This process ensures that the rename is properly reflected in the index, preparing it for a commit.
19+
///
20+
/// - Parameters:
21+
/// - directoryURL: The URL of the directory containing the git repository.
22+
/// - file: A `WorkingDirectoryFileChange` object representing the file whose changes are to be applied to the index.
23+
/// - Throws: An error if the git commands fail to execute.
24+
///
25+
/// # Example:
26+
/// ```
27+
/// let directoryURL = URL(fileURLWithPath: "/path/to/repo")
28+
/// let fileChange = WorkingDirectoryFileChange(path: "newName.txt", status: .renamed(oldName: "oldName.txt"))
29+
/// try await applyPatchToIndex(directoryURL: directoryURL, file: fileChange)
30+
/// ```
31+
func applyPatchToIndex(directoryURL: URL,
32+
file: WorkingDirectoryFileChange) throws {
33+
// If the file was a rename we have to recreate that rename since we've
34+
// just blown away the index.
35+
if file.status.kind == .renamed {
36+
if let renamedFile = file.status as? CopiedOrRenamedFileStatus {
37+
if renamedFile.kind == .renamed {
38+
try GitShell().git(args: ["add", "--u", "--", renamedFile.oldPath],
39+
path: directoryURL,
40+
name: #function)
41+
42+
// Figure out the blob oid of the removed file
43+
let oldFile = try GitShell().git(args: ["ls-tree", "HEAD", "--", renamedFile.oldPath],
44+
path: directoryURL,
45+
name: #function)
46+
47+
let info = oldFile.stdout.split(separator: "\t",
48+
maxSplits: 1,
49+
omittingEmptySubsequences: true)[0]
50+
let components = info.split(separator: " ",
51+
maxSplits: 3,
52+
omittingEmptySubsequences: true)
53+
let mode = components[0]
54+
let oid = components[2]
55+
56+
try GitShell().git(args: ["update-index",
57+
"--add",
58+
"--cacheinfo",
59+
String(mode),
60+
String(oid),
61+
file.path],
62+
path: directoryURL,
63+
name: #function)
64+
}
65+
}
66+
}
67+
68+
let applyArgs: [String] = [
69+
"apply",
70+
"--cached",
71+
"--unidiff-zero",
72+
"--whitespace=nowarn",
73+
"-"
74+
]
75+
76+
let diff = try GitDiff().getWorkingDirectoryDiff(directoryURL: directoryURL,
77+
file: file)
78+
79+
if let diff = diff as? TextDiff {
80+
switch diff.kind {
81+
case .image, .binary, .submodule:
82+
throw NSError(domain: "PatchError", code: 0, userInfo: [NSLocalizedDescriptionKey: "Can't create partial commit in binary file: \(file.path)"])
83+
case .unrenderable:
84+
throw NSError(domain: "PatchError", code: 1, userInfo: [NSLocalizedDescriptionKey: "File diff is too large to generate a partial commit: \(file.path)"])
85+
default:
86+
fatalError("Unknown diff kind: \(diff.kind)")
87+
}
88+
}
89+
90+
if let diff = diff as? TextDiff {
91+
let patch = try PatchFormatterParser().formatPatch(file: file,
92+
diff: diff)
93+
94+
try GitShell().git(args: applyArgs,
95+
path: directoryURL,
96+
name: #function,
97+
options: IGitExecutionOptions(stdin: patch))
98+
}
99+
}
100+
101+
/// Verifies if a patch can be applied cleanly to the current state of the repository.
102+
///
103+
/// This function uses the `git apply --check` command to test if the provided patch can be applied.
104+
/// The `--check` flag ensures that no changes are actually made to the files. It simply checks
105+
/// for potential conflicts. If the patch cannot be applied due to conflicts, the function returns `false`.
106+
///
107+
/// - Parameters:
108+
/// - directoryURL: The URL of the directory containing the git repository.
109+
/// - patch: A string containing the patch data.
110+
/// - Returns: `true` if the patch can be applied cleanly, or `false` if conflicts are detected.
111+
/// - Throws: An error if the git command fails for reasons other than the patch not applying.
112+
///
113+
/// # Example:
114+
/// ```
115+
/// let directoryURL = URL(fileURLWithPath: "/path/to/repo")
116+
/// let patchString = "diff --git a/file.txt b/file.txt..."
117+
/// let canApplyPatch = try checkPatch(directoryURL: directoryURL, patch: patchString)
118+
/// ```
119+
func checkPatch(directoryURL: URL,
120+
patch: String) throws -> Bool {
121+
let result = try GitShell().git(args: ["apply", "--check", "-"],
122+
path: directoryURL,
123+
name: #function,
124+
options: IGitExecutionOptions(
125+
expectedErrors: [GitError.PatchDoesNotApply]
126+
))
127+
128+
if result.gitError == GitError.PatchDoesNotApply {
129+
return false
130+
}
131+
132+
// If `apply --check` succeeds, then the patch applies cleanly.
133+
return true
134+
}
135+
136+
/// Discards selected changes from a file in the working directory of a git repository.
137+
///
138+
/// This function generates a patch representing the inverse of the selected changes and applies it
139+
/// to the file to discard those changes. If the selection results in no changes, the function does
140+
/// nothing. The function uses the `git apply` command with flags designed to apply a patch with
141+
/// a zero context and to suppress warnings about whitespace.
142+
///
143+
/// - Parameters:
144+
/// - directoryURL: The URL of the directory containing the git repository.
145+
/// - filePath: The path to the file within the repository from which changes will be discarded.
146+
/// - diff: An object conforming to the `ITextDiff` protocol representing the diff of the file.
147+
/// - selection: A `DiffSelection` object representing the selected changes to discard.
148+
/// - Throws: An error if the patch cannot be generated or applied.
149+
///
150+
/// # Example:
151+
/// ```
152+
/// let directoryURL = URL(fileURLWithPath: "/path/to/repo")
153+
/// let filePath = "file.txt"
154+
/// let diff = TextDiff(...) // Diff object conforming to ITextDiff
155+
/// let selection = DiffSelection(...) // Selection object specifying which changes to discard
156+
/// try discardChangesFromSelection(directoryURL: directoryURL,
157+
/// filePath: filePath,
158+
/// diff: diff,
159+
/// selection: selection)
160+
/// ```
161+
func discardChangesFromSelection(directoryURL: URL,
162+
filePath: String,
163+
diff: ITextDiff,
164+
selection: DiffSelection) throws {
165+
guard let patch = PatchFormatterParser().formatPatchToDiscardChanges(filePath: filePath,
166+
diff: diff,
167+
selection: selection) else {
168+
// When the patch is null we don't need to apply it since it will be a noop.
169+
return
170+
}
171+
172+
let args = ["apply", "--unidiff-zero", "--whitespace=nowarn", "-"]
173+
174+
try GitShell().git(args: args,
175+
path: directoryURL,
176+
name: #function,
177+
options: IGitExecutionOptions(stdin: patch))
178+
}
179+
}

0 commit comments

Comments
 (0)