├── .gitattributes
├── .github
└── workflows
│ ├── build-documentation.yaml
│ ├── build.yml
│ └── lint.yml
├── .gitignore
├── .swiftlint.yml
├── .swiftpm
└── xcode
│ └── package.xcworkspace
│ └── xcshareddata
│ └── IDEWorkspaceChecks.plist
├── LICENSE
├── Package.swift
├── README.md
├── Sources
└── Version-Control
│ ├── Base
│ ├── Actions
│ │ └── GitHub
│ │ │ └── GitHubActions.swift
│ ├── Commands
│ │ ├── Add.swift
│ │ ├── Apply.swift
│ │ ├── Branch.swift
│ │ ├── Check.swift
│ │ ├── Checkout-Index.swift
│ │ ├── Checkout.swift
│ │ ├── Cherry-Pick.swift
│ │ ├── Clone.swift
│ │ ├── Commit.swift
│ │ ├── Config.swift
│ │ ├── Description.swift
│ │ ├── Diff-Check.swift
│ │ ├── Diff-Index.swift
│ │ ├── Diff.swift
│ │ ├── Format-Patch.swift
│ │ ├── GitIgnore.swift
│ │ ├── GitLog.swift
│ │ ├── Init.swift
│ │ ├── Interpret-Trailers.swift
│ │ ├── LFS.swift
│ │ ├── Merge.swift
│ │ ├── Pull.swift
│ │ ├── Push.swift
│ │ ├── RM.swift
│ │ ├── Rebase.swift
│ │ ├── Reflog.swift
│ │ ├── Refs.swift
│ │ ├── Remote.swift
│ │ ├── Reset.swift
│ │ ├── Rev-List.swift
│ │ ├── Rev-Parse.swift
│ │ ├── Revert.swift
│ │ ├── Squash.swift
│ │ ├── Stage.swift
│ │ ├── Stash.swift
│ │ ├── Status.swift
│ │ ├── Submodule.swift
│ │ ├── Tag.swift
│ │ ├── Update-Index.swift
│ │ └── Update-Ref.swift
│ ├── Core
│ │ ├── Blob
│ │ │ └── Blob.swift
│ │ ├── Core.swift
│ │ ├── GitProcess.swift
│ │ ├── GitShell.swift
│ │ ├── IExecutionOptions.swift
│ │ ├── IGitExecutionOptions.swift
│ │ ├── IGitResult.swift
│ │ ├── IResult.swift
│ │ ├── Parsers
│ │ │ ├── DiffParser.swift
│ │ │ ├── GitCherryPickParser.swift
│ │ │ ├── GitDelimiterParser.swift
│ │ │ ├── GitErrorParser.swift
│ │ │ ├── GitRebaseParser.swift
│ │ │ ├── GitStatusParser.swift
│ │ │ └── PatchFormatterParser.swift
│ │ ├── ProcessError.swift
│ │ └── Progress
│ │ │ ├── From-Process.swift
│ │ │ ├── GitProgress.swift
│ │ │ └── LFSProgress.swift
│ └── Models
│ │ ├── CloneOptions.swift
│ │ ├── Commands
│ │ ├── Diff
│ │ │ ├── Diff Component Types
│ │ │ │ └── DiffImage.swift
│ │ │ ├── Diff Types
│ │ │ │ ├── IBinaryDiff.swift
│ │ │ │ ├── IImageDiff.swift
│ │ │ │ ├── ILargeTextDiff.swift
│ │ │ │ ├── ISubmoduleDiff.swift
│ │ │ │ ├── ITextDiff.swift
│ │ │ │ └── IUnrenderableDiff.swift
│ │ │ ├── DiffType.swift
│ │ │ ├── IDiff.swift
│ │ │ ├── IDiffTypes.swift
│ │ │ └── ITextDiffData.swift
│ │ ├── Log
│ │ │ └── IChangesetData.swift
│ │ ├── Rebase
│ │ │ ├── ComputedAction.swift
│ │ │ ├── GitRebaseSnapshot.swift
│ │ │ ├── RebaseInternalState.swift
│ │ │ ├── RebaseProgressOptions.swift
│ │ │ └── RebaseResult.swift
│ │ └── Status
│ │ │ ├── ConflictFilesDetails.swift
│ │ │ ├── IStatusEntry.swift
│ │ │ ├── IStatusHeader.swift
│ │ │ ├── StatusHeadersData.swift
│ │ │ ├── StatusResult.swift
│ │ │ └── WorkingDirectoryStatus.swift
│ │ ├── CommitHistory.swift
│ │ ├── CommitIdentity.swift
│ │ ├── Diff
│ │ ├── Diff-Data.swift
│ │ ├── Diff-Line.swift
│ │ ├── Diff-Selection.swift
│ │ ├── Helper
│ │ │ └── Diff-Helper.swift
│ │ └── Raw-Diff.swift
│ │ ├── Files
│ │ ├── AppFileStatus.swift
│ │ ├── CommittedFileChange.swift
│ │ ├── FileChange.swift
│ │ └── WorkingDirectoryFileChange.swift
│ │ ├── GitBranch.swift
│ │ ├── GitCommit.swift
│ │ ├── GitFileItem.swift
│ │ ├── GitType.swift
│ │ ├── IGitAccount.swift
│ │ ├── IProgress.swift
│ │ ├── IRemote.swift
│ │ ├── ManualConflictResolution.swift
│ │ └── Stash-Entry.swift
│ ├── Errors
│ ├── GitError.swift
│ ├── IndexError.swift
│ ├── NetworkingError.swift
│ └── ShellErrors.swift
│ ├── Services
│ ├── API
│ │ ├── BitBucket
│ │ │ ├── BitBucketAPI.swift
│ │ │ └── Interfaces
│ │ │ │ └── Repo
│ │ │ │ ├── IBitBucketAPIPullRequest.swift
│ │ │ │ ├── IBitbucketComment.swift
│ │ │ │ └── IBitbucketRendered.swift
│ │ ├── GitHub
│ │ │ ├── AuthorizationResponse.swift
│ │ │ ├── AuthorizationResponseKind.swift
│ │ │ ├── GitHubAPI.swift
│ │ │ ├── GithubNetworkingConstants.swift
│ │ │ ├── Interfaces
│ │ │ │ ├── Account
│ │ │ │ │ ├── IAPIEmail.swift
│ │ │ │ │ ├── IAPIFullIdentity.swift
│ │ │ │ │ ├── IAPIIdentity.swift
│ │ │ │ │ └── IAPIOrganization.swift
│ │ │ │ └── Repo
│ │ │ │ │ ├── Branch
│ │ │ │ │ └── IAPIBranch.swift
│ │ │ │ │ ├── IAPIFullRepository.swift
│ │ │ │ │ ├── IAPIMentionableResponse.swift
│ │ │ │ │ ├── IAPIRepository.swift
│ │ │ │ │ ├── IAPIRepositoryCloneInfo.swift
│ │ │ │ │ ├── IAPIRepositoryPermissions.swift
│ │ │ │ │ ├── Issues
│ │ │ │ │ └── IAPIIssue.swift
│ │ │ │ │ ├── Pull Request
│ │ │ │ │ ├── IAPIComment.swift
│ │ │ │ │ ├── IAPIPullRequest.swift
│ │ │ │ │ ├── IAPIPullRequestRef.swift
│ │ │ │ │ └── IAPIPullRequestReview.swift
│ │ │ │ │ ├── Push
│ │ │ │ │ └── IAPIPushControl.swift
│ │ │ │ │ ├── Ruleset
│ │ │ │ │ ├── IAPIRepoRule.swift
│ │ │ │ │ ├── IAPIRepoRuleMetadataParameters.swift
│ │ │ │ │ ├── IAPIRepoRuleset.swift
│ │ │ │ │ ├── IAPISlimRepoRuleset.swift
│ │ │ │ │ └── IRawAPIRepoRule.swift
│ │ │ │ │ └── Workflow
│ │ │ │ │ ├── IAPICheckSuite.swift
│ │ │ │ │ ├── IAPIRefCheckRun.swift
│ │ │ │ │ ├── IAPIRefCheckRunApp.swift
│ │ │ │ │ ├── IAPIRefCheckRunCheckSuite.swift
│ │ │ │ │ ├── IAPIRefCheckRunOutput.swift
│ │ │ │ │ ├── IAPIRefCheckRuns.swift
│ │ │ │ │ ├── IAPIRefStatus.swift
│ │ │ │ │ ├── IAPIRefStatusItem.swift
│ │ │ │ │ ├── IAPIWorkflowJob.swift
│ │ │ │ │ ├── IAPIWorkflowJobStep.swift
│ │ │ │ │ ├── IAPIWorkflowJobs.swift
│ │ │ │ │ ├── IAPIWorkflowRun.swift
│ │ │ │ │ └── IAPIWorkflowRuns.swift
│ │ │ └── Model
│ │ │ │ ├── Account
│ │ │ │ └── GithubAccount.swift
│ │ │ │ └── Repo
│ │ │ │ ├── Issues
│ │ │ │ └── APIIssueState.swift
│ │ │ │ ├── Pull Request
│ │ │ │ └── APIPullRequestReviewState.swift
│ │ │ │ ├── Ruleset
│ │ │ │ ├── APIRepoRuleMetadataOperator.swift
│ │ │ │ └── APIRepoRuleType.swift
│ │ │ │ └── Workflow
│ │ │ │ ├── APICheckConclusion.swift
│ │ │ │ ├── APICheckStatus.swift
│ │ │ │ └── APIRefState.swift
│ │ ├── Gitlab
│ │ │ └── GitlabAPI.swift
│ │ └── Global
│ │ │ └── Gravatar.swift
│ ├── Models
│ │ └── Github
│ │ │ ├── Actions
│ │ │ ├── Jobs
│ │ │ │ ├── Job.swift
│ │ │ │ ├── JobSteps.swift
│ │ │ │ └── Jobs.swift
│ │ │ └── Workflow
│ │ │ │ ├── Workflow.swift
│ │ │ │ ├── WorkflowRun.swift
│ │ │ │ ├── WorkflowRuns.swift
│ │ │ │ └── Workflows.swift
│ │ │ └── Auth
│ │ │ ├── 2FA.swift
│ │ │ └── GitHubEmail.swift
│ └── Networking
│ │ ├── AuroraNetworking.swift
│ │ ├── AuroraNetworkingConstants.swift
│ │ ├── AuroraNetworkingDebug.swift
│ │ ├── HTTPErrors.swift
│ │ └── HTTPMethod.swift
│ ├── Utils
│ ├── BranchUtil.swift
│ ├── CommandError.swift
│ ├── Extensions
│ │ ├── Date.swift
│ │ ├── FileManager.swift
│ │ └── String.swift
│ ├── FileUtils.swift
│ ├── Helpers
│ │ ├── DefaultBranch.swift
│ │ ├── GitAuthor.swift
│ │ ├── MediaDiff.swift
│ │ ├── Regex.swift
│ │ └── RemoveRemotePrefix.swift
│ ├── LiveShellClient.swift
│ └── ShellClient.swift
│ └── Version_Control.swift
└── Tests
└── Version-Control-Test
└── Services
├── API
└── GitHub
│ └── Mock Data
│ ├── GitHubAccountResponse.json
│ ├── ProtectedBranchesResponse.json
│ └── PushControlResponse.json
└── Auth
└── GitHub
└── GitHubEmailTest.swift
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.github/workflows/build-documentation.yaml:
--------------------------------------------------------------------------------
1 | name: build-documentation
2 |
3 | on:
4 | # Run on push to main branch
5 | push:
6 | branches:
7 | - main
8 |
9 | # Dispatch if triggered using Github (website)
10 | workflow_dispatch:
11 |
12 | jobs:
13 | Build-documentation:
14 | runs-on: macos-latest
15 | steps:
16 | - name: Build documentation
17 | uses: 0xWDG/build-documentation@main
18 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build
2 | on:
3 | push:
4 | branches: [ main ]
5 | pull_request:
6 | branches: [ main ]
7 | jobs:
8 | build:
9 | runs-on: macos-12
10 | timeout-minutes: 10 # If a build exceeds 10 mins, it probably isn't ever going to complete
11 | steps:
12 | - uses: actions/checkout@v2
13 | - name: MacOS Version
14 | run: sw_vers
15 | - name: Toolchain version
16 | run: swift -version
17 | - name: Build
18 | run: swift build
19 |
--------------------------------------------------------------------------------
/.github/workflows/lint.yml:
--------------------------------------------------------------------------------
1 | name: SwiftLint
2 | on:
3 | push:
4 | branches: [ main ]
5 | pull_request:
6 | branches: [ main ]
7 | jobs:
8 | lint:
9 | runs-on: macos-12
10 | timeout-minutes: 10 # If a build exceeds 10 mins, it probably isn't ever going to complete
11 | steps:
12 | - uses: actions/checkout@v2
13 | - name: MacOS Version
14 | run: sw_vers
15 | - name: Toolchain version
16 | run: swift -version
17 | - name: SwiftLint (version)
18 | run: swiftlint version
19 | - name: SwiftLint (GH Actions)
20 | run: swiftlint --reporter github-actions-logging --strict
21 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 | DerivedData/
7 | .swiftpm/config/registries.json
8 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
9 | .netrc
10 | .idea/
11 |
--------------------------------------------------------------------------------
/.swiftlint.yml:
--------------------------------------------------------------------------------
1 | # Swiftlint configuration file.
2 | # Part of AuroraEditor.
3 | # Please do not remove optional rules, feel free to add some if needed.
4 |
5 | # Disabled rule, reason.
6 | disabled_rules:
7 | - todo # New project, we have a lot of // TODO:
8 | - missing_docs # We miss a lot of docs right now
9 | - identifier_name # This should be fixed in a future version
10 | - force_try # This should be fixed in a future version
11 | # Exclude triggering type names.
12 | type_name:
13 | excluded:
14 | - ID
15 |
16 | # Exclude triggering identifier names.
17 | identifier_name:
18 | excluded:
19 | - id
20 | - vc
21 | - to
22 | # (short) File extensions:
23 | - c
24 | - m
25 | - h
26 | - js
27 | - md
28 | - py
29 | - go
30 | - ts
31 | - txt
32 | - sh
33 | - pf
34 | - r
35 | - q
36 | - tp
37 | - xl
38 | - hy
39 | - d
40 | - cs
41 |
42 | # cyclomatic_complexity (ignore case)
43 | cyclomatic_complexity:
44 | ignores_case_statements: true
45 |
46 | # Opt in rules, we want it more stricter.
47 | opt_in_rules:
48 | - empty_count
49 | - closure_spacing
50 | - contains_over_first_not_nil
51 | - missing_docs
52 | - modifier_order
53 | - convenience_type
54 | - pattern_matching_keywords
55 | - identical_operands
56 | - empty_string
57 |
58 | # Custom configuration for nesting, this needs to be removed at some point.
59 | nesting:
60 | type_level:
61 | warning: 2 # warning if you nest 2 level deep instead of 1
62 | error: 3 # error if you nest 3 level deep instead of 1
63 |
64 | # Custom rules
65 | custom_rules:
66 | # Prefer spaces over tabs.
67 | spaces_over_tabs:
68 | included: ".*\\.swift"
69 | name: "Spaces over Tabs"
70 | regex: "\t"
71 | message: "Prefer spaces for indents over tabs. See Xcode setting: 'Text Editing' -> 'Indentation'"
72 | severity: warning
73 |
74 | # @Something needs a new line
75 | at_attributes_newlining:
76 | name: "Significant attributes"
77 | message: "Significant @attributes should be on an extra line"
78 | included: ".*.swift"
79 | regex: '(@objc\([^\)]+\)|@nonobjc|@discardableResult|@propertyWrapper|@UIApplicationMain|@dynamicMemberLookup|@_cdecl\([^\)]+\))[^\n]'
80 | severity: error
81 |
82 | # forbid multiple empty lines
83 | multiple_empty_lines:
84 | included: ".*.swift"
85 | name: "Multiple Empty Lines"
86 | regex: '((?:\s*\n){3,})'
87 | message: "There are too many line breaks"
88 | severity: error
89 |
90 | # one space after a comma
91 | comma_space_rule:
92 | regex: ",[ ]{2,}"
93 | message: "Expected only one space after ',"
94 | severity: error
95 |
96 | # Disable usage of // swiftlint:disable (rulename)
97 | swiftlint_file_disabling:
98 | included: ".*.swift"
99 | name: "SwiftLint File Disabling"
100 | regex: "swiftlint:disable\\s"
101 | match_kinds: comment
102 | message: "Use swiftlint:disable:next or swiftlint:disable:this"
103 | severity: error
104 |
105 | # Disable Xcode placeholders like <#Description#>
106 | no_placeholders:
107 | included: ".*.swift"
108 | name: "No Placeholders"
109 | regex: "\\<\\#([a-zA-Z]+)\\#\\>"
110 | message: "Please do not use Xcode's placeholders."
111 | severity: warning
112 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version: 5.7
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "Version-Control",
8 | platforms: [.macOS(.v12)],
9 | products: [
10 | // Products define the executables and libraries a package produces, and make them visible to other packages.
11 | .library(
12 | name: "Version-Control",
13 | targets: ["Version-Control"])
14 | ],
15 | dependencies: [
16 | // Dependencies declare other packages that this package depends on.
17 | // .package(url: /* package url */, from: "1.0.0"),
18 | ],
19 | targets: [
20 | // Targets are the basic building blocks of a package. A target can define a module or a test suite.
21 | // Targets can depend on other targets in this package, and on products in packages this package depends on.
22 | .target(
23 | name: "Version-Control",
24 | dependencies: []),
25 | .testTarget(
26 | name: "Version-Control-Test",
27 | dependencies: [
28 | "Version-Control"
29 | ]
30 | )
31 | ]
32 | )
33 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | # Version Control Kit
6 |
7 | ## Overview
8 | The AuroraEditor Version Control Kit is a comprehensive tool designed to facilitate version control operations within the AuroraEditor environment. It enables actions such as committing, pulling, pushing, and fetching history for entire files or specific lines of code. This project is an extraction from the main AuroraEditor Repository and is currently under development.
9 |
10 | ## Features (Not limited to the below mentioned)
11 | - **Committing Changes**: Track and commit changes to your codebase.
12 | - **Pulling Updates**: Synchronize your local repository with changes from a remote repository.
13 | - **Pushing Changes**: Send your local commits to a remote repository.
14 | - **Fetching History**: Access the history of changes for files or individual lines of code.
15 | - **Git Services**: Allows you to make calls to GitHub, BitBucket and GitLab. (Create an issue if you want more Git providers added.)
16 |
17 | ## Installation
18 |
19 | ### Requirements
20 | - Swift 3.0 or later.
21 | - macOS Monterey or later
22 |
23 | ### Using Swift Package Manager
24 |
25 | 1. **Add Dependency**:
26 | Edit your `Package.swift` to include AuroraEditor Version Control Kit as a dependency:
27 |
28 | ```swift
29 | dependencies: [
30 | .package(url: "https://github.com/AuroraEditor/Version-Control-Kit.git", from: "Version-0")
31 | ]
32 | ```
33 |
34 | Else if you want the latest up to date version use the branch name:
35 |
36 | ```swift
37 | dependencies: [
38 | .package(url: "https://github.com/AuroraEditor/Version-Control-Kit.git", .branch("main"))
39 | ]
40 | ```
41 | ## Usage
42 |
43 | The AuroraEditor Version Control Kit's source code is in the process of being thoroughly documented. While many functions include detailed explanations, cautionary notes, and examples with their requirements, there are some areas still pending comprehensive documentation. These will be addressed and updated in due time. Users can look forward to a future How-To guide that will provide additional structured guidance on using the toolkit's capabilities.
44 |
45 | ## Socials
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 | ## Licenses
57 |
58 | ### Intellectual Property License
59 | - The AuroraEditor Version Control Logo is the copyright of AuroraEditor and Aurora Company.
60 |
61 | ### GNU Affero General Public License v3.0
62 | - This project is licensed under the GNU AGPL V3 License.
63 |
64 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Actions/GitHub/GitHubActions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GitHubActions.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/09/25.
6 | //
7 |
8 | import Foundation
9 | import AppKit
10 |
11 | public enum GitHubViewType: String {
12 | case tree
13 | case compare
14 | }
15 |
16 | public struct GitHubActions {
17 |
18 | internal func getBranchName(
19 | directoryURL: URL,
20 | completion: @escaping (Result) -> Void
21 | ) {
22 | Task {
23 | do {
24 | let branchName = try await Branch().getCurrentBranch(directoryURL: directoryURL)
25 | completion(.success(branchName))
26 | } catch {
27 | completion(.failure(error))
28 | }
29 | }
30 | }
31 |
32 | internal func getCurrentRepositoryGitHubURL(directoryURL: URL) throws -> String {
33 | let remoteUrls: [GitRemote] = try Remote().getRemotes(directoryURL: directoryURL)
34 |
35 | for remote in remoteUrls where remote.url.contains("github.com") {
36 | return remote.url
37 | }
38 |
39 | return ""
40 | }
41 |
42 | /// Open a specific branch of a GitHub repository in a web browser.
43 | ///
44 | /// This function constructs the URL for a specific branch of
45 | /// a GitHub repository based on the provided parameters and opens it in the default web browser.
46 | ///
47 | /// - Parameters:
48 | /// - viewType: The type of view to open on GitHub (e.g., code, commits, pulls).
49 | /// - directoryURL: The local directory URL of the Git repository.
50 | ///
51 | /// - Throws: An error if there's an issue with constructing the URL or opening it in the web browser.
52 | ///
53 | /// - Example:
54 | /// ```swift
55 | /// do {
56 | /// let viewType = GitHubViewType.commits
57 | /// let directoryURL = URL(fileURLWithPath: "/path/to/local/repository")
58 | /// try openBranchOnGitHub(viewType: viewType, directoryURL: directoryURL)
59 | /// } catch {
60 | /// print("Error: Unable to open the GitHub branch.")
61 | /// }
62 | public func openBranchOnGitHub(viewType: GitHubViewType,
63 | directoryURL: URL) throws {
64 | let htmlURL = try getCurrentRepositoryGitHubURL(directoryURL: directoryURL)
65 |
66 | var branchName = ""
67 |
68 | getBranchName(directoryURL: directoryURL) { result in
69 | switch result {
70 | case .success(let name):
71 | branchName = name
72 | case .failure(let error):
73 | branchName = ""
74 | }
75 | }
76 |
77 | let urlEncodedBranchName = branchName.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed)
78 |
79 | guard let encodedBranchName = urlEncodedBranchName else {
80 | return
81 | }
82 |
83 | let url = URL(string: "\(htmlURL)/\(viewType)/\(encodedBranchName)")
84 |
85 | NSWorkspace.shared.open(url!)
86 | }
87 |
88 | /// Open the GitHub issue creation page for the current repository in a web browser.
89 | ///
90 | /// This function constructs the URL for creating a new issue in
91 | /// the current repository on GitHub and opens it in the default web browser.
92 | ///
93 | /// - Parameter directoryURL: The local directory URL of the Git repository.
94 | ///
95 | /// - Throws: An error if there's an issue with constructing the URL or opening it in the web browser.
96 | ///
97 | /// - Example:
98 | /// ```swift
99 | /// do {
100 | /// let directoryURL = URL(fileURLWithPath: "/path/to/local/repository")
101 | /// try openIssueCreationOnGitHub(directoryURL: directoryURL)
102 | /// } catch {
103 | /// print("Error: Unable to open the GitHub issue creation page.")
104 | /// }
105 | public func openIssueCreationOnGitHub(directoryURL: URL) throws {
106 | let repositoryURL = try getCurrentRepositoryGitHubURL(directoryURL: directoryURL)
107 |
108 | let url = URL(string: "\(repositoryURL)/issues/new/choose")
109 |
110 | NSWorkspace.shared.open(url!)
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Commands/Add.swift:
--------------------------------------------------------------------------------
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 | }
30 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Commands/Check.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Check.swift
3 | // AuroraEditor
4 | //
5 | // Created by Nanashi Li on 2022/09/08.
6 | // Copyright © 2022 Aurora Company. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public struct Check {
12 |
13 | public init() {}
14 |
15 | /// Checks if a given workspace directory is a Git repository or a Git worktree.
16 | ///
17 | /// - Parameter workspaceURL: The URL of the workspace directory to be checked.
18 | ///
19 | /// - Returns: `true` if the workspace is a Git repository or worktree, `false` otherwise.
20 | ///
21 | /// - Note: This function checks the type of the workspace using `getRepositoryType`, \
22 | /// and if it's marked as unsafe by Git, \
23 | /// it falls back to a naive approximation by looking for the `.git` directory.
24 | ///
25 | /// - Example:
26 | /// ```swift
27 | /// let workspaceURL = URL(fileURLWithPath: "/path/to/workspace")
28 | ///
29 | /// if checkIfProjectIsRepo(workspaceURL: workspaceURL) {
30 | /// print("The workspace is a Git repository or worktree.")
31 | /// } else {
32 | /// print("The workspace is not a Git repository or worktree.")
33 | /// }
34 | /// ```
35 | ///
36 | /// - Warning:
37 | /// Ensure that the specified `workspaceURL` exists and is a valid directory.
38 | public func checkIfProjectIsRepo(workspaceURL: URL) -> Bool {
39 | do {
40 | let type = try RevParse().getRepositoryType(directoryURL: workspaceURL)
41 |
42 | switch type {
43 | case .missing:
44 | return false
45 | case .unsafe:
46 | return FileManager().directoryExistsAtPath("\(workspaceURL)/.git")
47 | case .bare, .regular:
48 | return true
49 | }
50 |
51 | } catch {
52 | print("We couldn't verify if the current project is a Git repo!")
53 | return false
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Commands/Checkout-Index.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/10/29.
6 | //
7 |
8 | import Foundation
9 |
10 | public struct CheckoutIndex {
11 |
12 | public init() {}
13 |
14 | public func checkoutIndex(directoryURL: URL,
15 | paths: [String]) async throws {
16 |
17 | if paths.isEmpty {
18 | return
19 | }
20 |
21 | let options: IGitExecutionOptions = IGitExecutionOptions(
22 | stdin: paths.joined(separator: "\0"),
23 | successExitCodes: Set([0, 1])
24 | )
25 |
26 | try GitShell().git(args: ["checkout-index", "-f", "-u", "-q", "--stdin", "-z"],
27 | path: directoryURL,
28 | name: #function,
29 | options: options)
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Commands/Clone.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Clone.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 Clone {
13 |
14 | public init() {}
15 |
16 | /// Clones a repository from a given url into to the specified path.
17 | ///
18 | /// @param url - The remote repository URL to clone from
19 | ///
20 | /// @param path - The destination path for the cloned repository. If the
21 | /// path does not exist it will be created. Cloning into an
22 | /// existing directory is only allowed if the directory is
23 | /// empty.
24 | ///
25 | /// @param options - Options specific to the clone operation, see the
26 | /// documentation for CloneOptions for more details.
27 | ///
28 | /// @param progressCallback - An optional function which will be invoked
29 | /// with information about the current progress
30 | /// of the clone operation. When provided this enables
31 | /// the '--progress' command line flag for
32 | /// 'git clone'.
33 | typealias CloneProgressCallback = (CloneProgress) -> Void
34 |
35 | let steps: [ProgressStep] = [
36 | ProgressStep(title: "remote: Compressing objects", weight: 0.1),
37 | ProgressStep(title: "Receiving objects", weight: 0.6),
38 | ProgressStep(title: "Resolving deltas", weight: 0.1),
39 | ProgressStep(title: "Checking out files", weight: 0.2)
40 | ]
41 |
42 | func clone(directoryURL: URL,
43 | path: String,
44 | options: CloneOptions,
45 | progressCallback: ((ICloneProgress) -> Void)? = nil) async throws {
46 | // let env = try await envForRemoteOperation(options.account, url)
47 | let defaultBranch = options.defaultBranch ?? (DefaultBranch().getDefaultBranch())
48 |
49 | var args = gitNetworkArguments + ["-c", "init.defaultBranch=\(defaultBranch)", "clone", "--recursive"]
50 | var gitOptions: IGitExecutionOptions = IGitExecutionOptions()
51 |
52 | if let progress = progressCallback {
53 | args.append("--progress")
54 |
55 | let title = "Cloning into \(path)"
56 | let kind = "clone"
57 |
58 | gitOptions = try FromProcess().executionOptionsWithProgress(
59 | options: gitOptions,
60 | parser: GitProgressParser(steps: steps),
61 | progressCallback: { progressInfo in
62 | var description: String = ""
63 |
64 | if let gitProgress = progressInfo as? IGitProgress, progressInfo.kind == "progress" {
65 | description = gitProgress.details.text
66 | } else if let gitOutput = progressInfo as? IGitOutput {
67 | description = gitOutput.text
68 | }
69 |
70 | let value = progressInfo.percent
71 |
72 | progressCallback?(CloneProgress(kind: kind, value: value, title: title, description: description))
73 | }
74 | )
75 |
76 | progressCallback?(CloneProgress(kind: kind, value: 0, title: title))
77 | }
78 |
79 | if let branch = options.branch {
80 | args += ["-b", branch]
81 | }
82 |
83 | args += ["--", directoryURL.relativePath.escapedWhiteSpaces(), path]
84 |
85 | try GitShell().git(args: args,
86 | path: directoryURL,
87 | name: #function,
88 | options: gitOptions)
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Commands/Description.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Description.swift
3 | // AuroraEditor
4 | //
5 | // Created by Nanashi Li on 2022/08/13.
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 | private let gitDescriptionPath = ".git/description"
13 |
14 | private let defaultGitDescription = "Unnamed repository; edit this file 'description' to name the repository.\n"
15 |
16 | public struct GitDescription {
17 |
18 | public init() {}
19 |
20 | /// Get the project's description from the `.git/description` file.
21 | ///
22 | /// This function retrieves the project's description from the `.git/description`
23 | /// file within a Git repository located at the specified `directoryURL`. \
24 | /// The description typically provides a brief overview of the project.
25 | ///
26 | /// - Parameter directoryURL: The URL of the directory containing the Git repository.
27 | ///
28 | /// - Throws:
29 | /// - An error of type `Error` if any issues occur during the description retrieval process.
30 | ///
31 | /// - Returns:
32 | /// The project's description as a string, or an empty string if the description \
33 | /// is not found or cannot be retrieved.
34 | ///
35 | /// - Example:
36 | /// ```swift
37 | /// let directoryURL = URL(fileURLWithPath: "/path/to/repo") // Replace with the path to the Git repository
38 | ///
39 | /// do {
40 | /// let projectDescription = try getGitDescription(directoryURL: directoryURL)
41 | /// if !projectDescription.isEmpty {
42 | /// print("Project Description: \(projectDescription)")
43 | /// } else {
44 | /// print("Project description not found.")
45 | /// }
46 | /// } catch {
47 | /// print("Error retrieving project description: \(error.localizedDescription)")
48 | /// }
49 | /// ```
50 | ///
51 | /// - Note:
52 | /// This function reads the content of the `.git/description` file in the Git repository specified by
53 | /// `directoryURL` and returns it as a string. \
54 | /// If the description is not found or cannot be retrieved, an empty string is returned.
55 | public func getGitDescription(directoryURL: URL) throws -> String {
56 | let path = try String(contentsOf: directoryURL) + gitDescriptionPath
57 |
58 | do {
59 | let data = try String(contentsOf: URL(string: path)!)
60 | if data == defaultGitDescription {
61 | return ""
62 | }
63 | return data
64 | } catch {
65 | return ""
66 | }
67 | }
68 |
69 | /// Write a project's description to the `.git/description` file within a Git repository.
70 | ///
71 | /// This function writes the provided `description` to the `.git/description` file within
72 | /// a Git repository located at the specified `directoryURL`. \
73 | /// The description typically provides a brief overview of the project.
74 | ///
75 | /// - Parameters:
76 | /// - directoryURL: The URL of the directory containing the Git repository.
77 | /// - description: The project's description to be written to the file.
78 | ///
79 | /// - Throws:
80 | /// - An error of type `Error` if any issues occur during the description writing process.
81 | ///
82 | /// - Example:
83 | /// ```swift
84 | /// let directoryURL = URL(fileURLWithPath: "/path/to/repo") // Replace with the path to the Git repository
85 | /// let projectDescription = "My Awesome Project" // Replace with the desired project description
86 | ///
87 | /// do {
88 | /// try writeGitDescription(directoryURL: directoryURL, description: projectDescription)
89 | /// print("Project Description has been updated.")
90 | /// } catch {
91 | /// print("Error writing project description: \(error.localizedDescription)")
92 | /// }
93 | /// ```
94 | ///
95 | /// - Note:
96 | /// This function writes the provided `description` to the `.git/description` file
97 | /// in the Git repository specified by `directoryURL`. \
98 | /// It does so by overwriting the existing content of the file, if any.
99 | public func writeGitDescription(directoryURL: URL,
100 | description: String) throws {
101 | let fullPath = try String(contentsOf: directoryURL) + gitDescriptionPath
102 | try description.write(toFile: fullPath, atomically: false, encoding: .utf8)
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Commands/Diff-Check.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Diff-Check.swift
3 | // AuroraEditor
4 | //
5 | // Created by Nanashi Li on 2022/08/29.
6 | // Copyright © 2022 Aurora Company. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public struct DiffCheck {
12 |
13 | public init() {}
14 |
15 | /// Matches a line reporting a leftover conflict marker
16 | /// and captures the name of the file
17 | let pattern = "^.+:(\\d+): leftover conflict marker$"
18 |
19 | /// Returns a list of files with conflict markers present
20 | public func getFilesWithConflictMarkers(directoryURL: URL) throws -> [String: Int] {
21 | let args = ["diff", "--check"]
22 |
23 | let output = try GitShell().git(args: args,
24 | path: directoryURL,
25 | name: #function,
26 | options: IGitExecutionOptions(successExitCodes: Set([0, 2])))
27 |
28 | let captures = Regex().getCaptures(text: output.stdout,
29 | expression: try NSRegularExpression(pattern: pattern,
30 | options: .caseInsensitive))
31 |
32 | if captures.isEmpty {
33 | return [:]
34 | }
35 |
36 | // Flatten the list (only does one level deep)
37 | let flatCaptures = captures.flatMap { $0 }
38 |
39 | var counted: [String: Int] = [:]
40 | for val in flatCaptures {
41 | counted[val, default: 0] += 1
42 | }
43 |
44 | return counted
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Commands/Diff-Index.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Diff-Index.swift
3 | // AuroraEditor
4 | //
5 | // Created by Nanashi Li on 2022/08/29.
6 | // Copyright © 2022 Aurora Company. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /// Possible statuses of an entry in Git
12 | public enum IndexStatus: Int {
13 | case unknown = 0
14 | case added
15 | case copied
16 | case deleted
17 | case modified
18 | case renamed
19 | case typeChanged
20 | case unmerged
21 | }
22 |
23 | public enum NoRenameIndexStatus {
24 | case added
25 | case deleted
26 | case modified
27 | case typeChanged
28 | case unmerged
29 | case unknown
30 | }
31 |
32 | public struct DiffIndex {
33 |
34 | public init() {}
35 |
36 | public func getIndexStatus(status: String) throws -> IndexStatus {
37 | switch status.substring(0) {
38 | case "A":
39 | return IndexStatus.added
40 | case "C":
41 | return IndexStatus.copied
42 | case "D":
43 | return IndexStatus.deleted
44 | case "M":
45 | return IndexStatus.modified
46 | case "R":
47 | return IndexStatus.renamed
48 | case "T":
49 | return IndexStatus.typeChanged
50 | case "U":
51 | return IndexStatus.unmerged
52 | case "X":
53 | return IndexStatus.unknown
54 | default:
55 | throw IndexError.unknownIndex("Unknown index status: \(status)")
56 | }
57 | }
58 |
59 | public func getNoRenameIndexStatus(_ status: String) throws -> NoRenameIndexStatus {
60 | do {
61 | let parsed = try getIndexStatus(status: status)
62 |
63 | switch parsed {
64 | case .unknown:
65 | return .unknown
66 | case .added:
67 | return .added
68 | case .copied, .renamed:
69 | throw IndexError.invalidStatus("Invalid index status for no-rename index status: \(parsed.rawValue)")
70 | case .deleted:
71 | return .deleted
72 | case .modified:
73 | return .modified
74 | case .typeChanged:
75 | return .typeChanged
76 | case .unmerged:
77 | return .unmerged
78 | }
79 | } catch {
80 | throw IndexError.invalidStatus("Unknown status: \(status)")
81 | }
82 | }
83 |
84 | /// The SHA for the nil tree
85 | public let nilTreeSHA = "4b825dc642cb6eb9a060e54bf8d69288fbee4904"
86 |
87 | /// Get the status of changes in the Git index (staging area).
88 | ///
89 | /// This function retrieves the status of changes in the Git index (staging area) for all tracked files.
90 | ///
91 | /// - Parameters:
92 | /// - directoryURL: The URL of the Git repository directory where the `git diff-index` command will be executed.
93 | ///
94 | /// - Returns: A dictionary where the keys are file paths and the values are `IndexStatus` \
95 | /// representing the status of each file in the index.
96 | ///
97 | /// - Throws: An error if there is a problem executing the `git diff-index` command or \
98 | /// if the Git repository is not in a valid state.
99 | ///
100 | /// - SeeAlso: `git diff-index` documentation for additional options and details.
101 | public func getIndexChanges(directoryURL: URL) throws -> [String: NoRenameIndexStatus] {
102 | let args = [
103 | "diff-index",
104 | "--cached",
105 | "name-status",
106 | "--no-renames",
107 | "-z"
108 | ]
109 |
110 | var result = try GitShell().git(args: args + ["HEAD", "--"],
111 | path: directoryURL,
112 | name: #function,
113 | options: IGitExecutionOptions(successExitCodes: Set([0, 128])))
114 |
115 | if result.exitCode == 128 {
116 | result = try GitShell().git(args: args + [nilTreeSHA],
117 | path: directoryURL,
118 | name: #function)
119 | }
120 |
121 | let pieces = result.stdout.split(separator: "\0")
122 |
123 | var map = [String: NoRenameIndexStatus]()
124 |
125 | for number in stride(from: 0, to: pieces.count - 1, by: 2) {
126 | let statusString = String(pieces[number])
127 | let path = String(pieces[number + 1])
128 | let status = try getIndexStatus(status: statusString)
129 |
130 | switch status {
131 | case .unknown:
132 | map[path] = .unknown
133 | case .added:
134 | map[path] = .added
135 | case .deleted:
136 | map[path] = .deleted
137 | case .modified:
138 | map[path] = .modified
139 | case .typeChanged:
140 | map[path] = .typeChanged
141 | case .unmerged:
142 | map[path] = .unmerged
143 | default:
144 | map[path] = .unknown
145 | }
146 | }
147 |
148 | return map
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Commands/Format-Patch.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Format-Patch.swift
3 | // AuroraEditor
4 | //
5 | // Created by Nanashi Li on 2022/08/13.
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 FormatPatch {
13 |
14 | public init() {}
15 |
16 | /// Creates a patch string representation of changes between two commits.
17 | ///
18 | /// This asynchronous function leverages the `GitShell` utility to run the `git format-patch`
19 | /// command, which generates a patch string for the changes between two specified revisions in a Git repository.
20 | ///
21 | /// - Parameters:
22 | /// - directoryURL: A `URL` pointing to the Git repository's directory.
23 | /// - base: A `String` representing the base commit or reference.
24 | /// - head: A `String` representing the head commit or reference.
25 | ///
26 | /// - Returns: A `String` containing the patch data.
27 | ///
28 | /// - Throws: An error if the `git` command fails or if there are issues accessing the repository.
29 | ///
30 | /// The function constructs a revision range from the base to the head parameters, then passes this along with
31 | /// other arguments to the `git` command via `GitShell`. The command specifies a unified diff with minimal
32 | /// context and directs the output to standard output instead of creating files. The function awaits the result and
33 | /// returns the standard output, which contains the patch data.
34 | ///
35 | /// This is an asynchronous function, and it must be called with `await` in an asynchronous context.
36 | /// The use of `try` indicates that the function can throw an error which must be handled by the caller.
37 | func formatPatch(directoryURL: URL,
38 | base: String,
39 | head: String) async throws -> String {
40 | let range = RevList().revRange(from: base, to: head)
41 | let output = try GitShell().git(args: ["format-patch", "--unified=1", "--minimal", "--stdout", range],
42 | path: directoryURL,
43 | name: #function)
44 | return output.stdout
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Commands/Init.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Init.swift
3 | // AuroraEditor
4 | //
5 | // Created by Nanashi Li on 2022/08/13.
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 Git {
13 |
14 | public init() {}
15 |
16 | /// Initialize a new Git repository in the specified directory.
17 | ///
18 | /// This function creates a new Git repository in the provided directory and configures the
19 | /// default branch name based on system settings. \
20 | /// If a Git repository already exists in the specified directory, this function has no effect.
21 | ///
22 | /// - Parameters:
23 | /// - directoryURL: The URL of the directory where the Git repository should be initialized.
24 | ///
25 | /// - Throws: An error if there was an issue initializing the Git repository.
26 | ///
27 | /// - Example:
28 | /// ```swift
29 | /// do {
30 | /// try initGitRepository(directoryURL: myProjectDirectoryURL)
31 | /// print("Git repository initialized successfully.")
32 | /// } catch {
33 | /// print("Error: \(error)")
34 | /// }
35 | /// ```
36 | ///
37 | /// - Note: If a Git repository already exists in the specified directory,
38 | /// this function will not reinitialize it and will have no effect.
39 | ///
40 | /// - Important: Make sure to call this function to initialize a new Git repository
41 | /// in a directory before performing Git operations on that directory.
42 | public func initGitRepository(directoryURL: URL) throws {
43 | try ShellClient().run(
44 | // swiftlint:disable:next line_length
45 | "cd \(directoryURL.relativePath.escapedWhiteSpaces());git -c init.defaultBranch=\(DefaultBranch().getDefaultBranch()) init"
46 | )
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Commands/Push.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Push.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/11/01.
6 | //
7 |
8 | import Foundation
9 |
10 | public struct PushOptions {
11 | /**
12 | * Force-push the branch without losing changes in the remote that
13 | * haven't been fetched.
14 | *
15 | * See https://git-scm.com/docs/git-push#Documentation/git-push.txt---no-force-with-lease
16 | */
17 | let forceWithLease: Bool = false
18 | }
19 |
20 | public struct Push {
21 |
22 | public init() {}
23 |
24 | public func push( // swiftlint:disable:this function_parameter_count
25 | directoryURL: URL,
26 | remote: IRemote,
27 | localBranch: String,
28 | remoteBranch: String?,
29 | tagsToPush: [String]?,
30 | options: PushOptions,
31 | progressCallback: ((IPushProgress) -> Void)? = nil
32 | ) throws {
33 | var args = gitNetworkArguments + [
34 | "push",
35 | remote.name,
36 | remoteBranch != nil ? "\(localBranch):\(remoteBranch!)" : localBranch
37 | ]
38 |
39 | if let tags = tagsToPush {
40 | args += tags
41 | }
42 |
43 | if remoteBranch == nil {
44 | args.append("--set-upstream")
45 | } else if options.forceWithLease {
46 | args.append("--force-with-lease")
47 | }
48 |
49 | // TODO: Add progress support
50 |
51 | let result = try GitShell().git(args: args,
52 | path: directoryURL,
53 | name: #function)
54 |
55 | if result.gitErrorDescription != nil {
56 | throw GitErrorParser(result: result,
57 | args: args)
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Commands/RM.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RM.swift
3 | // AuroraEditor
4 | //
5 | // Created by Nanashi Li on 2022/08/13.
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 RM { // swiftlint:disable:this type_name
13 |
14 | public init() {}
15 |
16 | /// Remove all files from the Git index.
17 | ///
18 | /// This function removes all files from the Git index (staging area) in a Git repository \
19 | /// located at the specified `directoryURL`. \
20 | /// The files are removed from the staging area while keeping them in the working directory.
21 | ///
22 | /// - Parameters:
23 | /// - directoryURL: The URL of the directory containing the Git repository.
24 | ///
25 | /// - Throws:
26 | /// - An error of type `Error` if any issues occur during the file removal from the index.
27 | ///
28 | /// - Example:
29 | /// ```swift
30 | /// let directoryURL = URL(fileURLWithPath: "/path/to/repo") // Replace with the path to the Git repository
31 | ///
32 | /// do {
33 | /// try unstageAllFiles(directoryURL: directoryURL)
34 | /// print("All files have been removed from the Git index.")
35 | /// } catch {
36 | /// print("Error removing files from the Git index: \(error.localizedDescription)")
37 | /// }
38 | /// ```
39 | ///
40 | /// - Note:
41 | /// This function uses the `git rm --cached -r -f .` command to remove all files from the Git \
42 | /// index while preserving them in the working directory.
43 | ///
44 | /// - Warning:
45 | /// Exercise caution when using this function, \
46 | /// as it can lead to the removal of all staged changes without committing them. \
47 | /// Make sure you understand the implications of unstaging files from the index.
48 | public func unstageAllFiles(directoryURL: URL) throws {
49 |
50 | // these flags are important:
51 | // --cached - to only remove files from the index
52 | // -r - to recursively remove files, in case files are in folders
53 | // -f - to ignore differences between working directory and index
54 | // which will block this
55 | try GitShell().git(args: ["rm",
56 | "--chached",
57 | "-r",
58 | "-f",
59 | "."],
60 | path: directoryURL,
61 | name: #function)
62 | }
63 |
64 | /// Remove a conflicted file from both the working tree and the Git index (staging area).
65 | ///
66 | /// This function removes a conflicted file specified by `file` from both the working tree and the \
67 | /// Git index (staging area) in a Git repository located at the specified `directoryURL`. \
68 | /// The file will be deleted from the working directory, and the removal will be staged for the next commit.
69 | ///
70 | /// - Parameters:
71 | /// - directoryURL: The URL of the directory containing the Git repository.
72 | /// - file: The `GitFileItem` representing the conflicted file to be removed.
73 | ///
74 | /// - Throws:
75 | /// - An error of type `Error` if any issues occur during the removal process.
76 | ///
77 | /// - Example:
78 | /// ```swift
79 | /// let directoryURL = URL(fileURLWithPath: "/path/to/repo") // Replace with the path to the Git repository
80 | /// let conflictedFile = GitFileItem(url: URL(fileURLWithPath: "path/to/conflicted/file"), gitStatus: .conflicted)
81 | ///
82 | /// do {
83 | /// try removeConflictedFile(directoryURL: directoryURL, file: conflictedFile)
84 | /// print("Conflicted file \(conflictedFile.url.path) has been removed.")
85 | /// } catch {
86 | /// print("Error removing conflicted file: \(error.localizedDescription)")
87 | /// }
88 | /// ```
89 | ///
90 | /// - Note:
91 | /// This function uses the `git rm` command with the `--` flag to remove the specified conflicted file \
92 | /// from both the working directory and the Git index.
93 | ///
94 | /// - Warning:
95 | /// Be cautious when using this function, as it permanently deletes the conflicted file \
96 | /// from both the working directory and the Git index.
97 | public func removeConflictedFile(directoryURL: URL,
98 | file: WorkingDirectoryFileChange) throws {
99 | try GitShell().git(args: ["rm",
100 | "--",
101 | file.path],
102 | path: directoryURL,
103 | name: #function)
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Commands/Revert.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Revert.swift
3 | // AuroraEditor
4 | //
5 | // Created by Nanashi Li on 2022/08/13.
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 GitRevert {
13 | /// Creates a new commit that reverts the changes of a previous commit
14 | ///
15 | /// @param sha - The SHA of the commit to be reverted
16 | func revertCommit(directoryURL: URL,
17 | commit: Commit,
18 | progressCallback: ((RevertProgress) -> Void)?
19 | ) throws {
20 | var args = gitNetworkArguments + ["revert"]
21 | if commit.parentSHAs.count > 1 {
22 | args += ["-m", "1"]
23 | }
24 |
25 | args.append(commit.sha)
26 |
27 | var opts: IGitExecutionOptions?
28 | if let progressCallback = progressCallback {
29 | opts = try FromProcess().executionOptionsWithProgress(
30 | options: IGitExecutionOptions(trackLFSProgress: true),
31 | parser: GitProgressParser(steps: [ProgressStep(title: "", weight: 0)]),
32 | progressCallback: { progress in
33 | var description: String = ""
34 | var title: String = ""
35 |
36 | if let gitProgress = progress as? IGitProgress, progress.kind == "progress" {
37 | description = gitProgress.details.text
38 | title = gitProgress.details.title
39 | } else if let gitOutput = progress as? IGitOutput {
40 | description = gitOutput.text
41 | title = ""
42 | }
43 |
44 | let value = progress.percent
45 |
46 | progressCallback(RevertProgress(kind: "revert",
47 | value: value,
48 | title: title,
49 | description: description))
50 | }
51 | )
52 | }
53 |
54 | try GitShell().git(args: args,
55 | path: directoryURL,
56 | name: #function,
57 | options: opts)
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Commands/Squash.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Squash.swift
3 | // AuroraEditor
4 | //
5 | // Created by Nanashi Li on 2022/08/13.
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 GitSquash {
13 |
14 | /// Squashes provided commits by calling interactive rebase.
15 | ///
16 | /// Goal is to replay the commits in order from oldest to newest to reduce
17 | /// conflicts with toSquash commits placed in the log at the location of the
18 | /// squashOnto commit.
19 | ///
20 | /// Example: A user's history from oldest to newest is A, B, C, D, E and they
21 | /// want to squash A and E (toSquash) onto C. Our goal: B, A-C-E, D. Thus,
22 | /// maintaining that A came before C and E came after C, placed in history at the
23 | /// the squashOnto of C.
24 | ///
25 | /// Also means if the last 2 commits in history are A, B, whether user squashes A
26 | /// onto B or B onto A. It will always perform based on log history, thus, B onto
27 | /// A.
28 | func squash( // swiftlint:disable:this function_body_length
29 | directoryURL: URL,
30 | toSquash: [Commit],
31 | squashOnto: Commit,
32 | lastRetainedCommitRef: String?,
33 | commitMessage: String,
34 | progressCallback: ((MultiCommitOperationProgress) -> Void)? = nil
35 | ) async throws -> RebaseResult {
36 | var messagePath: String?
37 | var todoPath: String?
38 | var result: RebaseResult = .error
39 |
40 | do {
41 | guard !toSquash.isEmpty else {
42 | throw NSError(
43 | domain: "com.auroraeditor.versioncontrolkit.squash",
44 | code: 1,
45 | userInfo: [NSLocalizedDescriptionKey: "No commits provided to squash."]
46 | )
47 | }
48 |
49 | let toSquashShas = Set(toSquash.map { $0.sha })
50 | guard !toSquashShas.contains(squashOnto.sha) else {
51 | throw NSError(
52 | domain: "com.auroraeditor.versioncontrolkit.squash",
53 | code: 2,
54 | userInfo: [
55 | NSLocalizedDescriptionKey: "The commits to squash cannot contain the commit to squash onto."
56 | ]
57 | )
58 | }
59 |
60 | let commits = try GitLog().getCommits(
61 | directoryURL: directoryURL,
62 | revisionRange: lastRetainedCommitRef == nil ? nil : "\(lastRetainedCommitRef!)..HEAD",
63 | limit: nil,
64 | skip: nil
65 | )
66 |
67 | guard !commits.isEmpty else {
68 | throw NSError(
69 | domain: "com.auroraeditor.versioncontrolkit.squash",
70 | code: 3,
71 | userInfo: [NSLocalizedDescriptionKey: "Could not find commits in log for last retained commit ref."]
72 | )
73 | }
74 |
75 | todoPath = try await FileUtils().writeToTempFile(content: "", tempFileName: "squashTodo")
76 |
77 | // Logic for building the todoPath content goes here
78 |
79 | if !commitMessage.trimmingCharacters(in: .whitespaces).isEmpty {
80 | messagePath = try await FileUtils().writeToTempFile(
81 | content: commitMessage,
82 | tempFileName: "squashCommitMessage"
83 | )
84 | }
85 |
86 | let gitEditor = messagePath != nil ? "cat \"\(messagePath!)\" >" : nil
87 |
88 | result = try Rebase().rebaseInteractive(
89 | directoryURL: directoryURL,
90 | pathOfGeneratedTodo: todoPath!,
91 | lastRetainedCommitRef: lastRetainedCommitRef,
92 | action: "Squash",
93 | gitEditor: gitEditor ?? ":",
94 | progressCallback: progressCallback,
95 | commits: toSquash + [squashOnto]
96 | )
97 | } catch {
98 | print("Error occurred: \(error)")
99 | return .error
100 | }
101 |
102 | return result
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Commands/Stage.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Stage.swift
3 | // AuroraEditor
4 | //
5 | // Created by Nanashi Li on 2022/08/13.
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 GitStage {
13 |
14 | public init() {}
15 |
16 | /// Stages a file with the given manual resolution method.
17 | /// Useful for resolving binary conflicts at commit-time.
18 | func stageManualConflictResolution(directoryURL: URL,
19 | file: WorkingDirectoryFileChange,
20 | manualResolution: ManualConflictResolution) throws {
21 | guard let fileStatus = file.status else {
22 | print("File status is nil")
23 | return
24 | }
25 | if !isConflictedFileStatus(fileStatus) {
26 | print("Tried to manually resolve unconflicted file (\(file.path))")
27 | return
28 | }
29 |
30 | guard let conflictedStatus = fileStatus as? ConflictsWithMarkers else {
31 | print("Failed to cast to ConflictsWithMarkers")
32 | return
33 | }
34 |
35 | if isConflictWithMarkers(conflictedStatus) && conflictedStatus.conflictMarkerCount == 0 {
36 | // If the file was manually resolved, no further action is required.
37 | return
38 | }
39 |
40 | let chosen = manualResolution == .theirs
41 | ? conflictedStatus.entry.details.them
42 | : conflictedStatus.entry.details.us
43 |
44 | let addedInBoth = conflictedStatus.entry.details.us == GitStatusEntry.added
45 | && conflictedStatus.entry.details.them == GitStatusEntry.added
46 |
47 | if chosen == .updatedButUnmerged || addedInBoth {
48 | try GitCheckout().checkoutConflictedFile(directoryURL: directoryURL,
49 | file: file,
50 | resolution: manualResolution)
51 | }
52 |
53 | switch chosen {
54 | case .deleted:
55 | try RM().removeConflictedFile(directoryURL: directoryURL,
56 | file: file)
57 | case .added, .updatedButUnmerged:
58 | try Add().addConflictedFile(directoryURL: directoryURL,
59 | file: file)
60 | default:
61 | fatalError("Unaccounted for git status entry possibility")
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Commands/Submodule.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Submodule.swift
3 | // AuroraEditor
4 | //
5 | // Created by Nanashi Li on 2022/08/13.
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 Submodule {
13 |
14 | public init() {}
15 |
16 | func listSubmodules(directoryURL: URL) throws -> [SubmoduleEntry] {
17 | let submodulesFile = FileManager.default.fileExists(
18 | atPath: directoryURL.appendingPathComponent(".gitmodules").path
19 | )
20 | var isDirectory: ObjCBool = true
21 | let submodulesDir = FileManager.default.fileExists(
22 | atPath: directoryURL.appendingPathComponent(".git/modules").path,
23 | isDirectory: &isDirectory
24 | )
25 |
26 | if !submodulesFile && !submodulesDir {
27 | print("No submodules found. Skipping \"git submodule status\"")
28 | return []
29 | }
30 |
31 | let gitArgs = ["submodule", "status", "--"]
32 | let result = try GitShell().git(args: gitArgs,
33 | path: directoryURL,
34 | name: #function,
35 | options: IGitExecutionOptions(successExitCodes: Set([0, 128])))
36 |
37 | if result.exitCode == 128 {
38 | // Unable to parse submodules in the repository, giving up
39 | return []
40 | }
41 |
42 | var submodules = [SubmoduleEntry]()
43 |
44 | // entries are of the format:
45 | // 1eaabe34fc6f486367a176207420378f587d3b48 git (v2.16.0-rc0)
46 | //
47 | // first character:
48 | // - " " if no change
49 | // - "-" if the submodule is not initialized
50 | // - "+" if the currently checked out submodule commit does not match the SHA-1 found
51 | // in the index of the containing repository
52 | // - "U" if the submodule has merge conflicts
53 | //
54 | // then the 40-character SHA represents the current commit
55 | //
56 | // then the path to the submodule
57 | //
58 | // then the output of `git describe` for the submodule in braces
59 | // we're not leveraging this in the app, so go and read the docs
60 | // about it if you want to learn more:
61 | //
62 | // https://git-scm.com/docs/git-describe
63 | let statusRe = try NSRegularExpression(pattern: #".([^ ]+) (.+) \((.+?)\)"#, options: [])
64 |
65 | let stdout = result.stdout
66 | let range = NSRange(stdout.startIndex.. String {
60 | let components = result.stdout.components(separatedBy: "]")
61 | guard components.count >= 2,
62 | let shaPart = components.first?.components(separatedBy: " "),
63 | shaPart.count >= 2 else {
64 | return ""
65 | }
66 | return shaPart[1]
67 | }
68 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Core/GitProcess.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GitProcess.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/10/31.
6 | //
7 |
8 | import Foundation
9 |
10 | struct GitProcess {
11 |
12 | // func executionOptionsWithProgress(
13 | // options: IGitExecutionOptions,
14 | // parser: GitProgressParser,
15 | // progressCallback: @escaping (GitProgressKind) -> Void
16 | // ) throws -> IGitExecutionOptions {
17 | // var lfsProgressPath: String? = nil
18 | // var env = [String: String]()
19 | //
20 | // if options.trackLFSProgress ?? false {
21 | // do {
22 | // lfsProgressPath = try createLFSProgressFile()
23 | // env["GIT_LFS_PROGRESS"] = lfsProgressPath
24 | // } catch {
25 | // print("Error writing LFS progress file", error)
26 | // env["GIT_LFS_PROGRESS"] = nil
27 | // }
28 | // }
29 | //
30 | // return merge(options, IGitExecutionOptions(
31 | // processCallback: createProgressProcessCallback(
32 | // parser: parser,
33 | // lfsProgressPath: lfsProgressPath,
34 | // progressCallback: progressCallback
35 | // ),
36 | // env: merge(options.env, env)
37 | // ))
38 | // }
39 | //
40 | // func createProgressProcessCallback(parser: GitProgressParser,
41 | // lfsProgressPath: String?,
42 | // progressCallback: @escaping (GitProgressKind) -> Void) -> (Process) -> Void {
43 | // return { process in
44 | // var lfsProgressActive = false
45 | //
46 | // if let lfsProgressPath = lfsProgressPath {
47 | // let lfsParser = GitLFSProgressParser()
48 | // let disposable = tailByLine(lfsProgressPath) { line in
49 | // let progress = lfsParser.parse(line)
50 | //
51 | // if progress.kind == "progress" {
52 | // lfsProgressActive = true
53 | // progressCallback(progress)
54 | // }
55 | // }
56 | //
57 | // process.terminationHandler = { _ in
58 | // disposable.dispose()
59 | // // the order of these callbacks is important because
60 | // // - unlink can only be done on files
61 | // // - rmdir can only be done when the directory is empty
62 | // // - we don't want to surface errors to the user if something goes
63 | // // wrong (these files can stay in TEMP and be cleaned up eventually)
64 | // do {
65 | // try FileManager.default.removeItem(atPath: lfsProgressPath)
66 | // let directory = (lfsProgressPath as NSString).deletingLastPathComponent
67 | // try? FileManager.default.removeItem(atPath: directory)
68 | // } catch {
69 | // // Handle any errors here as needed
70 | // }
71 | // }
72 | // }
73 | //
74 | // if let stderr = process.standardError {
75 | // let lineReader = LineReader(stream: stderr)
76 | //
77 | // while let line = lineReader.readLine() {
78 | // let progress = parser.parse(line)
79 | //
80 | // if lfsProgressActive {
81 | // // While we're sending LFS progress, we don't want to mix
82 | // // any non-progress events in with the output, or we'll get
83 | // // flickering between the indeterminate LFS progress and
84 | // // the regular progress.
85 | // if progress.kind == "context" {
86 | // continue
87 | // }
88 | //
89 | // let title = progress.details.title
90 | // let done = progress.details.done
91 | //
92 | // // The 'Filtering content' title happens while the LFS
93 | // // filter is running, and when it's done, we know that the
94 | // // filter is done, but until then, we don't want to display
95 | // // it for the same reason that we don't want to display
96 | // // the context above.
97 | // if title == "Filtering content" {
98 | // if done {
99 | // lfsProgressActive = false
100 | // }
101 | // continue
102 | // }
103 | // }
104 | //
105 | // progressCallback(.progress(progress))
106 | // }
107 | // }
108 | // }
109 | // }
110 | }
111 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Core/IExecutionOptions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IExecutionOptions.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/10/31.
6 | //
7 |
8 | import Foundation
9 |
10 | protocol IExecutionOptions {
11 | var env: [String: String]? { get set }
12 | var stdin: String? { get set }
13 | var stdinEncoding: String? { get set }
14 | var maxBuffer: Int? { get set }
15 | var processCallback: ((Process) -> Void)? { get set }
16 | }
17 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Core/IGitExecutionOptions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/10/31.
6 | //
7 |
8 | import Foundation
9 |
10 | public struct IGitExecutionOptions: IExecutionOptions {
11 | var env: [String: String]?
12 | var stdin: String?
13 | var stdinEncoding: String?
14 | var maxBuffer: Int?
15 | var processCallback: ((Process) -> Void)?
16 | var successExitCodes: Set?
17 | var expectedErrors: Set?
18 | var trackLFSProgress: Bool?
19 | }
20 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Core/IGitResult.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IGitResult.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/10/31.
6 | //
7 |
8 | import Foundation
9 |
10 | public struct IGitResult: IResult {
11 | public var stdout: String
12 | public var stderr: String
13 | public let exitCode: Int
14 | public let gitError: GitError?
15 | public var gitErrorDescription: String?
16 | public let combinedOutput: String
17 | public let path: String
18 | }
19 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Core/IResult.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IResult.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/10/31.
6 | //
7 |
8 | import Foundation
9 |
10 | /** The result of shelling out to git. */
11 | protocol IResult {
12 | /** The standard output from git. */
13 | var stdout: String { get }
14 |
15 | /** The standard error output from git. */
16 | var stderr: String { get }
17 |
18 | /** The exit code of the git process. */
19 | var exitCode: Int { get }
20 | }
21 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Core/Parsers/GitCherryPickParser.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GitCherryPickParser.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/11/19.
6 | //
7 |
8 | import Foundation
9 |
10 | class GitCherryPickParser {
11 | private let commits: [Commit]
12 | private var count: Int = 0
13 |
14 | init(commits: [Commit],
15 | count: Int = 0) {
16 | self.commits = commits
17 | self.count = count
18 | }
19 |
20 | func parse(line: String) -> MultiCommitOperationProgress? {
21 | // Regular expression to match the expected cherry-pick line format
22 | let cherryPickRe = try! NSRegularExpression(pattern: "^\\[(.*\\s.*)\\]")
23 |
24 | // Range to search in the line
25 | let range = NSRange(location: 0, length: line.utf16.count)
26 |
27 | // Check if the line matches the expected format
28 | if let match = cherryPickRe.firstMatch(in: line, options: [], range: range), match.numberOfRanges > 1 {
29 | self.count += 1 // Increment the count for each matched line
30 |
31 | let summaryIndex = self.count - 1
32 | let summary = summaryIndex < commits.count ? commits[summaryIndex].summary : ""
33 | let value = Double(self.count) / Double(commits.count)
34 |
35 | return MultiCommitOperationProgress(
36 | kind: "multiCommitOperation",
37 | currentCommitSummary: summary,
38 | position: self.count,
39 | totalCommitCount: self.commits.count,
40 | value: Int(round(value * 100)) / 100
41 | )
42 | }
43 |
44 | // Return nil if the line doesn't match
45 | return nil
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Core/Parsers/GitDelimiterParser.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GitDelimiterParser.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/10/31.
6 | //
7 |
8 | import Foundation
9 |
10 | /**
11 | A utility struct for parsing Git output with custom delimiters.
12 | */
13 | struct GitDelimiterParser {
14 |
15 | /**
16 | * Create a new parser suitable for parsing --format output from commands such
17 | * as `git log`, `git stash`, and other commands that are not derived from
18 | * `ref-filter`.
19 | *
20 | * Returns a tuple with the arguments that need to be appended to the git
21 | * call and the parse function itself
22 | *
23 | * - Parameter fields: A dictionary keyed on the friendly name of the value being
24 | * parsed with the value being the format string of said value.
25 | *
26 | * Example:
27 | *
28 | * `let (args, parse) = createLogParser(["sha": "%H"])`
29 | */
30 | func createLogParser(
31 | _ fields: [T: String]
32 | ) -> (formatArgs: [String], parse: (String) -> [[T: String]]) {
33 | let keys = Array(fields.keys)
34 | let format = fields.values.joined(separator: "%x00")
35 | let formatArgs = ["-z", "--format=\(format)"]
36 |
37 | let parse: (String) -> [[T: String]] = { value in
38 | let records = value.components(separatedBy: "\0")
39 | var entries = [[T: String]]()
40 |
41 | for i in stride(from: 0, to: records.count - keys.count, by: keys.count) {
42 | var entry = [T: String]()
43 | for (ix, key) in keys.enumerated() {
44 | entry[key] = records[i + ix]
45 | }
46 | entries.append(entry)
47 | }
48 |
49 | return entries
50 | }
51 |
52 | return (formatArgs, parse)
53 | }
54 |
55 | /**
56 | * Create a new parser suitable for parsing --format output from commands such
57 | * as `git for-each-ref`, `git branch`, and other commands that are not derived
58 | * from `git log`.
59 | *
60 | * Returns a tuple with the arguments that need to be appended to the git
61 | * call and the parse function itself
62 | *
63 | * - Parameter fields: A dictionary keyed on the friendly name of the value being
64 | * parsed with the value being the format string of said value.
65 | *
66 | * Example:
67 | *
68 | * `let (args, parse) = createForEachRefParser(["sha": "%(objectname)"])`
69 | */
70 | func createForEachRefParser(
71 | _ fields: [T: String]
72 | ) -> (formatArgs: [String], parse: (String) -> [[T: String]]) {
73 | let keys = Array(fields.keys)
74 | let format = fields.values.joined(separator: "%00")
75 | let formatArgs = ["--format=%00\(format)%00"]
76 |
77 | let parse: (String) -> [[T: String]] = { value in
78 | let records = value.components(separatedBy: "\0")
79 | var entries = [[T: String]]()
80 |
81 | var entry: [T: String]?
82 | var consumed = 0
83 |
84 | // start at 1 to avoid 0 modulo X problem. The first record is guaranteed
85 | // to be empty anyway (due to %00 at the start of --format)
86 | if records.count > 2 {
87 | for i in 1..<(records.count - 1) {
88 | if i % (keys.count + 1) == 0 {
89 | if records[i] != "\n" {
90 | fatalError("Expected newline")
91 | }
92 | continue
93 | }
94 |
95 | entry = entry ?? [T: String]()
96 | let key = keys[consumed % keys.count]
97 | entry![key] = records[i]
98 | consumed += 1
99 |
100 | if consumed % keys.count == 0 {
101 | entries.append(entry!)
102 | entry = nil
103 | }
104 | }
105 | }
106 |
107 | return entries
108 | }
109 |
110 | return (formatArgs, parse)
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Core/Parsers/GitErrorParser.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GitErrorParser.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/10/31.
6 | //
7 |
8 | import Foundation
9 |
10 | class GitErrorParser: Error {
11 | /// The result from the failed command.
12 | let result: IGitResult
13 |
14 | /// The args for the failed command.
15 | let args: [String]
16 |
17 | /// Whether or not the error message is just the raw output of the git command.
18 | let isRawMessage: Bool
19 |
20 | init(result: IGitResult, args: [String]) {
21 | var rawMessage = true
22 | var message = ""
23 |
24 | if let gitErrorDescription = result.gitErrorDescription {
25 | message = gitErrorDescription
26 | rawMessage = false
27 | } else if !result.combinedOutput.isEmpty {
28 | message = result.combinedOutput
29 | } else if !result.stderr.isEmpty {
30 | message = result.stderr
31 | } else if !result.stdout.isEmpty {
32 | message = result.stdout
33 | } else {
34 | message = "Unknown error"
35 | rawMessage = false
36 | }
37 |
38 | self.result = result
39 | self.args = args
40 | self.isRawMessage = rawMessage
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Core/Parsers/GitRebaseParser.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GitRebaseParser.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/11/01.
6 | //
7 |
8 | import Foundation
9 |
10 | public func formatRebaseValue(value: Double) -> Double {
11 | // Clamp the value between 0 and 1
12 | let clampedValue = max(0, min(value, 1))
13 | // Round to two decimal places
14 | let roundedValue = (clampedValue * 100).rounded() / 100
15 | return roundedValue
16 | }
17 |
18 | class GitRebaseParser {
19 | private let commits: [Commit]
20 |
21 | init(commits: [Commit]) {
22 | self.commits = commits
23 | }
24 |
25 | func parse(line: String) -> MultiCommitOperationProgress? {
26 | guard let rebasingRe = try? NSRegularExpression(pattern: "Rebasing \\((\\d+)/(\\d+)\\)") else {
27 | print("Failed to create regular expression for rebasing")
28 | return nil
29 | }
30 | let range = NSRange(location: 0, length: line.utf16.count)
31 |
32 | if let match = rebasingRe.firstMatch(in: line, options: [], range: range),
33 | match.numberOfRanges == 3,
34 | let rebasedCommitCountRange = Range(match.range(at: 1), in: line),
35 | let totalCommitCountRange = Range(match.range(at: 2), in: line),
36 | let rebasedCommitCount = Int(line[rebasedCommitCountRange]),
37 | let totalCommitCount = Int(line[totalCommitCountRange]) {
38 |
39 | let currentCommitSummary = commits.indices.contains(rebasedCommitCount - 1)
40 | ? commits[rebasedCommitCount - 1].summary
41 | : ""
42 | let progress = Double(rebasedCommitCount) / Double(totalCommitCount)
43 | let value = formatRebaseValue(value: progress)
44 |
45 | return MultiCommitOperationProgress(
46 | kind: "multiCommitOperation",
47 | currentCommitSummary: currentCommitSummary,
48 | position: rebasedCommitCount,
49 | totalCommitCount: totalCommitCount,
50 | value: Int(value)
51 | )
52 | }
53 | return nil
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Core/ProcessError.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ProcessError.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2024/07/02.
6 | //
7 |
8 | // Process Specific Errors
9 | public enum ProcessError: Error {
10 | case launchFailed(String)
11 | case timeout
12 | case unexpectedExitCode(Int)
13 | case outputParsingFailed
14 | }
15 |
16 | extension ProcessError {
17 | var errorDescription: String? {
18 | switch self {
19 | case .launchFailed(let reason):
20 | return "Failed to launch process: \(reason)"
21 | case .timeout:
22 | return "Process execution timed out"
23 | case .unexpectedExitCode(let code):
24 | return "Process exited with unexpected code: \(code)"
25 | case .outputParsingFailed:
26 | return "Failed to parse process output"
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Core/Progress/From-Process.swift:
--------------------------------------------------------------------------------
1 | //
2 | // From-Process.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/11/12.
6 | //
7 |
8 | import Foundation
9 |
10 | struct FromProcess {
11 |
12 | func executionOptionsWithProgress(
13 | options: IGitExecutionOptions,
14 | parser: GitProgressParser,
15 | progressCallback: @escaping (GitParsingResult) -> Void
16 | ) throws -> IGitExecutionOptions {
17 | var lfsProgressPath: String?
18 | var env = [String: String]()
19 | if options.trackLFSProgress! {
20 | do {
21 | lfsProgressPath = try LFSProgress().createLFSProgressFile()
22 | env["GIT_LFS_PROGRESS"] = lfsProgressPath
23 | } catch {
24 | print("Error writing LFS progress file: \(error)")
25 | env["GIT_LFS_PROGRESS"] = nil
26 | }
27 | }
28 |
29 | var mergedEnv = options.env ?? [:]
30 | mergedEnv.merge(env) { (_, new) in new }
31 |
32 | let mergedOptions = IGitExecutionOptions(env: mergedEnv, trackLFSProgress: options.trackLFSProgress)
33 | return mergedOptions
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Core/Progress/LFSProgress.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/11/12.
6 | //
7 |
8 | import Foundation
9 |
10 | struct IFileProgress {
11 | /// The number of bytes that have been transferred for this file.
12 | var transferred: Int
13 |
14 | /// The total size of the file in bytes.
15 | var size: Int
16 |
17 | /// Whether this file has been transferred fully.
18 | var done: Bool
19 | }
20 |
21 | struct LFSProgress {
22 |
23 | private var files = [String: IFileProgress]()
24 |
25 | let LFSProgressLineRe = try! NSRegularExpression(
26 | pattern: "^(.+?)\\s{1}(\\d+)\\/(\\d+)\\s{1}(\\d+)\\/(\\d+)\\s{1}(.+)$",
27 | options: []
28 | )
29 |
30 | func createLFSProgressFile() throws -> String {
31 | let tempDirectoryURL = FileManager.default.temporaryDirectory
32 | let lfsProgressURL = tempDirectoryURL.appendingPathComponent("AuroraEditor-lfs-progress-\(UUID().uuidString)")
33 |
34 | // Ensure the directory exists
35 | try FileManager.default.createDirectory(at: lfsProgressURL.deletingLastPathComponent(),
36 | withIntermediateDirectories: true)
37 |
38 | // Create the file if it does not exist
39 | if !FileManager.default.fileExists(atPath: lfsProgressURL.path) {
40 | FileManager.default.createFile(atPath: lfsProgressURL.path,
41 | contents: nil)
42 | } else {
43 | // If file exists, throw an error
44 | throw NSError(domain: "com.auroraeditor.editor",
45 | code: 0,
46 | userInfo: [NSLocalizedDescriptionKey: "File already exists"])
47 | }
48 |
49 | return lfsProgressURL.path
50 | }
51 |
52 | mutating func parse(line: String) -> GitParsingResult {
53 | let matches = LFSProgressLineRe.matches(in: line, range: NSRange(line.startIndex..., in: line))
54 |
55 | guard let match = matches.first, match.numberOfRanges == 7,
56 | let directionRange = Range(match.range(at: 1), in: line),
57 | let estimatedFileCountRange = Range(match.range(at: 3), in: line),
58 | let fileTransferredRange = Range(match.range(at: 4), in: line),
59 | let fileSizeRange = Range(match.range(at: 5), in: line),
60 | let fileNameRange = Range(match.range(at: 6), in: line),
61 | let estimatedFileCount = Int(line[estimatedFileCountRange]),
62 | let fileTransferred = Int(line[fileTransferredRange]),
63 | let fileSize = Int(line[fileSizeRange]) else {
64 | return IGitOutput(kind: "context", percent: 0, text: line)
65 | }
66 |
67 | let direction = String(line[directionRange])
68 | let fileName = String(line[fileNameRange])
69 | files[fileName] = IFileProgress(transferred: fileTransferred, size: fileSize, done: fileTransferred == fileSize)
70 |
71 | var totalTransferred = 0
72 | var totalEstimated = 0
73 | var finishedFiles = 0
74 |
75 | let fileCount = max(estimatedFileCount, files.count)
76 |
77 | for file in files.values {
78 | totalTransferred += file.transferred
79 | totalEstimated += file.size
80 | finishedFiles += file.done ? 1 : 0
81 | }
82 |
83 | let transferProgress = "\(totalTransferred) / \(totalEstimated)"
84 |
85 | let percentComplete = totalEstimated > 0 ? Int((Double(totalTransferred) / Double(totalEstimated)) * 100) : nil
86 |
87 | let verb = directionToHumanFacingVerb(direction: direction)
88 | let info = IGitProgressInfo(title: "\(verb) \"\(fileName)\"",
89 | value: totalTransferred,
90 | total: totalEstimated,
91 | percent: percentComplete,
92 | done: finishedFiles == fileCount,
93 | // swiftlint:disable:next line_length
94 | text: "\(verb) \(fileName) (\(finishedFiles) out of an estimated \(fileCount) completed, \(transferProgress))")
95 |
96 | return IGitProgress(kind: "progress", percent: info.percent ?? 0, details: info)
97 | }
98 |
99 | private func directionToHumanFacingVerb(direction: String) -> String {
100 | switch direction {
101 | case "download":
102 | return "Downloading"
103 | case "upload":
104 | return "Uploading"
105 | case "checkout":
106 | return "Checking out"
107 | default:
108 | return "Downloading"
109 | }
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Models/CloneOptions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CloneOptions.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/10/31.
6 | //
7 |
8 | import Foundation
9 |
10 | struct CloneOptions {
11 | let branch: String?
12 | let defaultBranch: String?
13 | }
14 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Models/Commands/Diff/Diff Component Types /DiffImage.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DiffImage.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/11/25.
6 | //
7 |
8 | import Foundation
9 |
10 | /**
11 | * A container for holding an image for display in the application
12 | */
13 | struct DiffImage {
14 | public var contents: String
15 | public var mediaType: String
16 | public var bytes: Int
17 |
18 | /**
19 | * @param contents The base64 encoded contents of the image.
20 | * @param mediaType The data URI media type, so the browser can render the image correctly.
21 | * @param bytes Size of the file in bytes.
22 | */
23 | public init(contents: String,
24 | mediaType: String,
25 | bytes: Int) {
26 | self.contents = contents
27 | self.mediaType = mediaType
28 | self.bytes = bytes
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Models/Commands/Diff/Diff Types/IBinaryDiff.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IBinaryDiff.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/11/25.
6 | //
7 |
8 | import Foundation
9 |
10 | public class IBinaryDiff: IDiff {
11 | public var kind: DiffType = .binary
12 | }
13 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Models/Commands/Diff/Diff Types/IImageDiff.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IImageDiff.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/11/25.
6 | //
7 |
8 | import Foundation
9 |
10 | public struct IImageDiff: IDiff {
11 | public var kind: DiffType = .image
12 |
13 | /**
14 | * The previous image, if the file was modified or deleted
15 | *
16 | * Will be undefined for an added image
17 | */
18 | var previous: DiffImage?
19 |
20 | /**
21 | * The current image, if the file was added or modified
22 | *
23 | * Will be undefined for a deleted image
24 | */
25 | var current: DiffImage?
26 | }
27 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Models/Commands/Diff/Diff Types/ILargeTextDiff.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ILargeTextDiff.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/11/25.
6 | //
7 |
8 | import Foundation
9 |
10 | public class ILargeTextDiff: ITextDiffData, IDiff, TextDiff {
11 | public var kind: DiffType = .largeText
12 | }
13 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Models/Commands/Diff/Diff Types/ISubmoduleDiff.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ISubmoduleDiff.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/11/25.
6 | //
7 |
8 | import Foundation
9 |
10 | public struct ISubmoduleDiff: IDiff {
11 | public var kind: DiffType = .submodule
12 |
13 | /** Full path of the submodule */
14 | var fullPath: String
15 |
16 | /** Path of the repository within its container repository */
17 | var path: String
18 |
19 | /** URL of the submodule */
20 | var url: String?
21 |
22 | /** Status of the submodule */
23 | var status: SubmoduleStatus
24 |
25 | /** Previous SHA of the submodule, or null if it hasn't changed */
26 | var oldSHA: String?
27 |
28 | /** New SHA of the submodule, or null if it hasn't changed */
29 | var newSHA: String?
30 | }
31 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Models/Commands/Diff/Diff Types/ITextDiff.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ITextDiff.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/11/25.
6 | //
7 |
8 | import Foundation
9 |
10 | public class ITextDiff: ITextDiffData, TextDiff, IDiff {
11 | public var kind: DiffType = .text
12 | }
13 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Models/Commands/Diff/Diff Types/IUnrenderableDiff.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IUnrenderableDiff.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/11/25.
6 | //
7 |
8 | import Foundation
9 |
10 | public class IUnrenderableDiff: IDiff {
11 | public var kind: DiffType = .unrenderable
12 | }
13 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Models/Commands/Diff/DiffType.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DiffType.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/11/25.
6 | //
7 |
8 | import Foundation
9 |
10 | public enum DiffType {
11 | /// Changes to a text file, which may be partially selected for commit
12 | case text
13 | /// Changes to a file with a known extension, which can be viewed in the editor
14 | case image
15 | /// Changes to an unknown file format, which Git is unable to present in a human-friendly format
16 | case binary
17 | /// Change to a repository which is included as a submodule of this repository
18 | case submodule
19 | /// Diff is large enough to degrade ux if rendered
20 | case largeText
21 | /// Diff that will not be rendered
22 | case unrenderable
23 | }
24 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Models/Commands/Diff/IDiff.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/11/25.
6 | //
7 |
8 | import Foundation
9 |
10 | public protocol IDiff {
11 | var kind: DiffType { get set }
12 | }
13 |
14 | public struct Diff: IDiff {
15 | public var kind: DiffType
16 | }
17 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Models/Commands/Diff/IDiffTypes.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IDiffTypes.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/11/25.
6 | //
7 |
8 | import Foundation
9 |
10 | public enum IDiffTypes {
11 | case text(ITextDiff)
12 | case image(IImageDiff)
13 | case binary(IBinaryDiff)
14 | case large(ILargeTextDiff)
15 | case unrenderable(IUnrenderableDiff)
16 | }
17 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Models/Commands/Diff/ITextDiffData.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ITextDiffData.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/11/25.
6 | //
7 |
8 | import Foundation
9 |
10 | /// Data returned as part of a textual diff from Aurora Editor
11 | public class ITextDiffData {
12 | /// The unified text diff - including headers and context
13 | var text: String
14 | /// The diff contents organized by hunk - how the git CLI outputs to the caller
15 | var hunks: [DiffHunk]
16 | /// A warning from Git that the line endings have changed in this file and will affect the commit
17 | var lineEndingsChange: LineEndingsChange?
18 | /// The largest line number in the diff
19 | var maxLineNumber: Int
20 | /// Whether or not the diff has invisible bidi characters
21 | var hasHiddenBidiChars: Bool
22 |
23 | init(text: String, hunks: [DiffHunk],
24 | lineEndingsChange: LineEndingsChange? = nil,
25 | maxLineNumber: Int,
26 | hasHiddenBidiChars: Bool) {
27 | self.text = text
28 | self.hunks = hunks
29 | self.lineEndingsChange = lineEndingsChange
30 | self.maxLineNumber = maxLineNumber
31 | self.hasHiddenBidiChars = hasHiddenBidiChars
32 | }
33 | }
34 |
35 | public protocol TextDiff: ITextDiffData {
36 | var kind: DiffType { get set }
37 | }
38 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Models/Commands/Log/IChangesetData.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IChangesetData.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/11/25.
6 | //
7 |
8 | import Foundation
9 |
10 | struct IChangesetData {
11 | /** Files changed in the changeset. */
12 | let files: [CommittedFileChange]
13 |
14 | /** Number of lines added in the changeset. */
15 | let linesAdded: Int
16 |
17 | /** Number of lines deleted in the changeset. */
18 | let linesDeleted: Int
19 | }
20 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Models/Commands/Rebase/ComputedAction.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ComputedAction.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/11/20.
6 | //
7 |
8 | import Foundation
9 |
10 | enum ComputedAction {
11 | case clean([Commit])
12 | case conflicts
13 | case invalid
14 | case loading
15 | }
16 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Models/Commands/Rebase/GitRebaseSnapshot.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GitRebaseSnapshot.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/11/20.
6 | //
7 |
8 | import Foundation
9 |
10 | struct GitRebaseSnapshot {
11 | let commits: [Commit]
12 | let progress: MultiCommitOperationProgress
13 | }
14 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Models/Commands/Rebase/RebaseInternalState.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RebaseInternalState.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/11/20.
6 | //
7 |
8 | import Foundation
9 |
10 | public struct RebaseInternalState {
11 | /// The branch containing commits that should be rebased
12 | let targetBranch: String
13 | /// The commit ID of the base branch, to be used as a starting point for the rebase.
14 | let baseBranchTip: String
15 | /// The commit ID of the target branch at the start of the rebase, which points to the original commit history.
16 | let originalBranchTip: String
17 | }
18 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Models/Commands/Rebase/RebaseProgressOptions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RebaseProgressOptions.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/11/20.
6 | //
7 |
8 | import Foundation
9 |
10 | struct RebaseProgressOptions {
11 | let commits: [Commit]
12 | let progressCallback: (MultiCommitOperationProgress) -> Void
13 | }
14 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Models/Commands/Rebase/RebaseResult.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RebaseResult.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/11/20.
6 | //
7 |
8 | import Foundation
9 |
10 | /// The app-specific results from attempting to rebase a repository.
11 | enum RebaseResult {
12 | /// Git completed the rebase without reporting any errors, and the caller can signal success to the user.
13 | case completedWithoutError
14 |
15 | /// Git completed the rebase without reporting any errors, \
16 | /// but the branch was already up to date and there was nothing to do.
17 | case alreadyUpToDate
18 |
19 | /// The rebase encountered conflicts while attempting to rebase, \
20 | /// and these need to be resolved by the user before the rebase can continue.
21 | case conflictsEncountered
22 |
23 | /// The rebase was not able to continue as tracked files were not staged in the index.
24 | case outstandingFilesNotStaged
25 |
26 | /// The rebase was not attempted because it could not check the status of the repository. \
27 | /// The caller needs to confirm the repository is in a usable state.
28 | case aborted
29 |
30 | /// An unexpected error as part of the rebase flow was caught and handled.
31 | ///
32 | /// Check the logs to find the relevant Git details.
33 | case error
34 | }
35 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Models/Commands/Status/ConflictFilesDetails.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ConflictFilesDetails.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/11/21.
6 | //
7 |
8 | import Foundation
9 |
10 | struct ConflictFilesDetails {
11 | let conflictCountsByPath: [String: Int]
12 | let binaryFilePaths: [String]
13 | }
14 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Models/Commands/Status/IStatusEntry.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StatusEntry.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/11/25.
6 | //
7 |
8 | import Foundation
9 |
10 | enum StatusEntryKind {
11 | case entry
12 | }
13 |
14 | protocol IStatusEntry {
15 | var kind: StatusEntryKind { get set }
16 | var path: String { get set }
17 | var statusCode: String { get set }
18 | var submoduleStatusCode: String { get set }
19 | var oldPath: String? { get set }
20 | }
21 |
22 | struct StatusEntry: IStatusEntry {
23 | var kind: StatusEntryKind
24 | var path: String
25 | var statusCode: String
26 | var submoduleStatusCode: String
27 | var oldPath: String?
28 | }
29 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Models/Commands/Status/IStatusHeader.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StatusHeader.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/11/21.
6 | //
7 |
8 | import Foundation
9 |
10 | protocol IStatusHeader {
11 | var kind: String { get set }
12 | var value: String { get set }
13 | }
14 |
15 | struct StatusHeader: IStatusHeader {
16 | var kind: String
17 | var value: String
18 | }
19 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Models/Commands/Status/StatusHeadersData.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StatusHeadersData.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/11/21.
6 | //
7 |
8 | import Foundation
9 |
10 | struct StatusHeadersData {
11 | let currentBranch: String?
12 | let currentUpstreamBranch: String?
13 | let currentTip: String?
14 | let branchAheadBehind: IAheadBehind?
15 | let match: [String]?
16 |
17 | public init() {
18 | self.currentBranch = nil
19 | self.currentUpstreamBranch = nil
20 | self.currentTip = nil
21 | self.branchAheadBehind = nil
22 | self.match = nil
23 | }
24 |
25 | public init(currentBranch: String?,
26 | currentUpstreamBranch: String?,
27 | currentTip: String?,
28 | branchAheadBehind: IAheadBehind?,
29 | match: [String]?) {
30 | self.currentBranch = currentBranch
31 | self.currentUpstreamBranch = currentUpstreamBranch
32 | self.currentTip = currentTip
33 | self.branchAheadBehind = branchAheadBehind
34 | self.match = match
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Models/Commands/Status/StatusResult.swift:
--------------------------------------------------------------------------------
1 | //
2 | // StatusResult.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/11/21.
6 | //
7 |
8 | import Foundation
9 |
10 | public struct StatusResult {
11 | /// The name of the current branch.
12 | public let currentBranch: String?
13 |
14 | /// The name of the current upstream branch.
15 | public let currentUpstreamBranch: String?
16 |
17 | /// The SHA of the tip commit of the current branch.
18 | public let currentTip: String?
19 |
20 | /// Information on how many commits ahead and behind the currentBranch is compared to the currentUpstreamBranch.
21 | public let branchAheadBehind: IAheadBehind?
22 |
23 | /// True if the repository exists at the given location.
24 | public let exists: Bool
25 |
26 | /// True if the repository is in a conflicted state.
27 | public let mergeHeadFound: Bool
28 |
29 | /// True if a merge --squash operation is started.
30 | public let squashMsgFound: Bool
31 |
32 | /// Details about the rebase operation, if found.
33 | public let rebaseInternalState: RebaseInternalState?
34 |
35 | /// True if the repository is in a cherry-picking state.
36 | public let isCherryPickingHeadFound: Bool
37 |
38 | /// The absolute path to the repository's working directory.
39 | public let workingDirectory: WorkingDirectoryStatus
40 |
41 | /// Whether conflicting files are present in the repository.
42 | public let doConflictedFilesExist: Bool
43 | }
44 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Models/Commands/Status/WorkingDirectoryStatus.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WorkingDirectoryStatus.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/11/25.
6 | //
7 |
8 | import Foundation
9 |
10 | public struct WorkingDirectoryStatus {
11 | let files: [WorkingDirectoryFileChange]
12 | let includeAll: Bool?
13 |
14 | init(files: [WorkingDirectoryFileChange],
15 | includeAll: Bool? = true) {
16 | self.files = files
17 | self.includeAll = includeAll
18 | }
19 |
20 | // Computed property to create a map from file ID to index.
21 | private var fileIxById: [String: Int] {
22 | var map = [String: Int]()
23 | for (index, file) in files.enumerated() {
24 | map[file.id] = index
25 | }
26 | return map
27 | }
28 |
29 | // Static function to create a new instance with files.
30 | static func fromFiles(_ files: [WorkingDirectoryFileChange]) -> WorkingDirectoryStatus {
31 | return WorkingDirectoryStatus(files: files, includeAll: getIncludeAllState(files))
32 | }
33 |
34 | // Function to update the include state of all files.
35 | func withIncludeAllFiles(includeAll: Bool) -> WorkingDirectoryStatus {
36 | let newFiles = files.map { $0.withIncludeAll(include: includeAll) }
37 | return WorkingDirectoryStatus(files: newFiles, includeAll: includeAll)
38 | }
39 |
40 | // Function to find a file with a given ID.
41 | func findFileWithID(_ id: String) -> WorkingDirectoryFileChange? {
42 | guard let index = fileIxById[id] else { return nil }
43 | return files.indices.contains(index) ? files[index] : nil
44 | }
45 |
46 | // Function to find the index of a file with a given ID.
47 | func findFileIndexByID(_ id: String) -> Int {
48 | return fileIxById[id] ?? -1
49 | }
50 | }
51 |
52 | func getIncludeAllState(_ files: [WorkingDirectoryFileChange]) -> Bool? {
53 | if files.isEmpty {
54 | return true
55 | }
56 |
57 | let allSelected = files.allSatisfy {
58 | $0.selection.getSelectionType() == .all
59 | }
60 | let noneSelected = files.allSatisfy {
61 | $0.selection.getSelectionType() == .none
62 | }
63 |
64 | if allSelected {
65 | return true
66 | } else if noneSelected {
67 | return false
68 | } else {
69 | return nil
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Models/CommitHistory.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CommitHistory.swift
3 | // AuroraEditorModules/Git
4 | //
5 | // Created by Marco Carnevali on 27/03/22.
6 | //
7 |
8 | import Foundation.NSDate
9 |
10 | /// Model class to help map commit history log data
11 | public struct CommitHistory: Equatable, Hashable, Identifiable {
12 | public var id = UUID()
13 | public let hash: String
14 | public let commitHash: String
15 | public let message: String
16 | public let author: String
17 | public let authorEmail: String
18 | public let commiter: String
19 | public let commiterEmail: String
20 | public let remoteURL: URL?
21 | public let date: Date
22 | public let isMerge: Bool?
23 |
24 | public init(hash: String,
25 | commitHash: String,
26 | message: String,
27 | author: String,
28 | authorEmail: String,
29 | commiter: String,
30 | commiterEmail: String,
31 | remoteURL: URL?,
32 | date: Date,
33 | isMerge: Bool?) {
34 | self.hash = hash
35 | self.commitHash = commitHash
36 | self.message = message
37 | self.author = author
38 | self.authorEmail = authorEmail
39 | self.commiter = commiter
40 | self.commiterEmail = commiterEmail
41 | self.remoteURL = remoteURL
42 | self.date = date
43 | self.isMerge = isMerge
44 | }
45 |
46 | public var commitBaseURL: URL? {
47 | if let remoteURL = remoteURL {
48 | if remoteURL.absoluteString.contains("github") {
49 | return parsedRemoteUrl(domain: "https://github.com", remote: remoteURL)
50 | }
51 | if remoteURL.absoluteString.contains("bitbucket") {
52 | return parsedRemoteUrl(domain: "https://bitbucket.org", remote: remoteURL)
53 | }
54 | if remoteURL.absoluteString.contains("gitlab") {
55 | return parsedRemoteUrl(domain: "https://gitlab.com", remote: remoteURL)
56 | }
57 | // TODO: Implement other git clients other than github, bitbucket here
58 | }
59 | return nil
60 | }
61 |
62 | private func parsedRemoteUrl(domain: String, remote: URL) -> URL {
63 | // There are 2 types of remotes - https and ssh. While https has URL in its name, ssh doesnt.
64 | // Following code takes remote name in format profileName/repoName and prepends according domain
65 | var formattedRemote = remote
66 | if formattedRemote.absoluteString.starts(with: "git@") {
67 | let parts = formattedRemote.absoluteString.components(separatedBy: ":")
68 | formattedRemote = URL.init(fileURLWithPath: "\(domain)/\(parts[parts.count - 1])")
69 | }
70 |
71 | return formattedRemote.deletingPathExtension().appendingPathComponent("commit")
72 | }
73 |
74 | public var remoteString: String {
75 | if let remoteURL = remoteURL {
76 | if remoteURL.absoluteString.contains("github") {
77 | return "GitHub"
78 | }
79 | if remoteURL.absoluteString.contains("bitbucket") {
80 | return "BitBucket"
81 | }
82 | if remoteURL.absoluteString.contains("gitlab") {
83 | return "GitLab"
84 | }
85 | }
86 | return "Remote"
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Models/CommitIdentity.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CommitIdentity.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/09/24.
6 | //
7 |
8 | import Foundation
9 |
10 | /**
11 | * A tuple of name, email, and date for the author or commit
12 | * info in a commit.
13 | */
14 | public struct CommitIdentity: Codable, Hashable {
15 | public let name: String
16 | public let email: String
17 | public let date: Date
18 | public let tzOffset: Int
19 |
20 | // Initialize the struct
21 | init(name: String, email: String, date: Date, tzOffset: Int = TimeZone.current.secondsFromGMT()) {
22 | self.name = name
23 | self.email = email
24 | self.date = date
25 | self.tzOffset = tzOffset
26 | }
27 |
28 | /**
29 | * Parses a Git ident string (GIT_AUTHOR_IDENT or GIT_COMMITTER_IDENT)
30 | * into a commit identity. Throws an error if identify string is invalid.
31 | */
32 | static func parseIdentity(identity: String) throws -> CommitIdentity {
33 | // See fmt_ident in ident.c:
34 | // https://github.com/git/git/blob/3ef7618e6/ident.c#L346
35 | //
36 | // Format is "NAME DATE"
37 | // Jane Doe 1475670580 +0200
38 | //
39 | // Note that `git var` will strip any < and > from the name and email, see:
40 | // https://github.com/git/git/blob/3ef7618e6/ident.c#L396
41 | //
42 | // Note also that this expects a date formatted with the RAW option in git see:
43 | // https://github.com/git/git/blob/35f6318d4/date.c#L191
44 | let pattern = #"^(.*?) <(.*?)> (\d+) (\+|-)?(\d{2})(\d{2})"#
45 |
46 | if let regex = try? NSRegularExpression(pattern: pattern, options: []) {
47 | if let match = regex.firstMatch(
48 | in: identity,
49 | options: [],
50 | range: NSRange(location: 0, length: identity.utf16.count)
51 | ) {
52 | let name = (identity as NSString).substring(with: match.range(at: 1))
53 | let email = (identity as NSString).substring(with: match.range(at: 2))
54 | let timestamp = TimeInterval((identity as NSString).substring(with: match.range(at: 3))) ?? 0
55 |
56 | // Convert seconds since epoch to milliseconds
57 | let date = Date(timeIntervalSince1970: timestamp)
58 |
59 | // Extract the timezone offset
60 | let tzSign = (identity as NSString).substring(with: match.range(at: 4)) == "-" ? -1 : 1
61 | let tzHH = (identity as NSString).substring(with: match.range(at: 5))
62 | let tzmm = (identity as NSString).substring(with: match.range(at: 6))
63 |
64 | if let tzHours = Int(tzHH), let tzMinutes = Int(tzmm) {
65 | let tzOffset = tzSign * (tzHours * 60 + tzMinutes)
66 |
67 | return CommitIdentity(name: name, email: email, date: date, tzOffset: tzOffset)
68 | }
69 | }
70 | }
71 |
72 | throw NSError(domain: "", code: 0, userInfo: ["errorDescription": "Couldn't parse identity \(identity)"])
73 | }
74 |
75 | // Convert the struct to a string
76 | func toString() -> String {
77 | return "\(name) <\(email)>"
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Models/Diff/Diff-Data.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Diff-Data.swift
3 | // AuroraEditor
4 | //
5 | // Created by Nanashi Li on 2022/08/29.
6 | // Copyright © 2022 Aurora Company. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | private let maximumDiffStringSize = 268435441
12 |
13 | public enum LineEndingType: String {
14 | // swiftlint:disable:next identifier_name
15 | case cr = "CR"
16 | // swiftlint:disable:next identifier_name
17 | case lf = "LF"
18 | case crlf = "CRLF"
19 | }
20 |
21 | public class LineEndingsChange {
22 | var from: LineEndingType
23 | var to: LineEndingType
24 |
25 | init(from: LineEndingType,
26 | to: LineEndingType) {
27 | self.from = from
28 | self.to = to
29 | }
30 | }
31 |
32 | /// Parse the line ending string into an enum value (or `null` if unknown)
33 | public func parseLineEndingText(text: String) -> LineEndingType? {
34 | let input = text.trimmingCharacters(in: .whitespacesAndNewlines)
35 | switch input {
36 | case "CR":
37 | return .cr
38 | case "LF":
39 | return .lf
40 | case "CRLF":
41 | return .crlf
42 | default:
43 | return nil
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Models/Diff/Diff-Line.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Diff-Line.swift
3 | // AuroraEditor
4 | //
5 | // Created by Nanashi Li on 2022/08/29.
6 | // Copyright © 2022 Aurora Company. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /// Indicate what a line in the diff represents
12 | enum DiffLineType {
13 | case context
14 | case add
15 | case delete
16 | case hunk
17 | }
18 |
19 | /// Track details related to each line in the diff
20 | class DiffLine {
21 | var text: String
22 | var type: DiffLineType
23 | // Line number in the original diff patch (before expanding it), or nil if
24 | // it was added as part of a diff expansion action./
25 | var originalLineNumber: Int?
26 | var oldLineNumber: Int?
27 | var newLineNumber: Int?
28 | var noTrailingNewLine: Bool = false
29 |
30 | init(text: String, type: DiffLineType, originalLineNumber: Int? = nil,
31 | oldLineNumber: Int? = nil, newLineNumber: Int? = nil,
32 | noTrailingNewLine: Bool) {
33 | self.text = text
34 | self.type = type
35 | self.originalLineNumber = originalLineNumber
36 | self.oldLineNumber = oldLineNumber
37 | self.newLineNumber = newLineNumber
38 | self.noTrailingNewLine = noTrailingNewLine
39 | }
40 |
41 | public func withNoTrailingNewLine(noTrailingNewLine: Bool) -> DiffLine {
42 | return DiffLine(text: self.text,
43 | type: self.type,
44 | originalLineNumber: self.originalLineNumber,
45 | oldLineNumber: self.oldLineNumber,
46 | newLineNumber: self.newLineNumber,
47 | noTrailingNewLine: noTrailingNewLine)
48 | }
49 |
50 | public func isIncludeableLine() -> Bool {
51 | return self.type == DiffLineType.add || self.type == DiffLineType.delete
52 | }
53 |
54 | /// The content of the line, i.e., without the line type marker.
55 | public func content() -> String {
56 | return self.text.substring(1)
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Models/Diff/Helper/Diff-Helper.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Diff-Helper.swift
3 | // AuroraEditor
4 | //
5 | // Created by Nanashi Li on 2022/08/29.
6 | // Copyright © 2022 Aurora Company. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /// Utility function for getting the digit count of the largest line number in an array of diff hunks
12 | public func getLargestLineNumber(hunks: [DiffHunk]) -> Int {
13 | if hunks.isEmpty {
14 | return 0
15 | }
16 |
17 | // swiftlint:disable:next identifier_name
18 | for i in stride(from: hunks.count - 1, to: 0, by: -2) {
19 | let hunk = hunks[i]
20 |
21 | // swiftlint:disable:next identifier_name
22 | for j in stride(from: hunk.lines.count - 1, to: 0, by: -1) {
23 | let line = hunk.lines[j]
24 |
25 | let newLineNumber = line.newLineNumber ?? 0
26 | let oldLineNumber = line.oldLineNumber ?? 0
27 | return newLineNumber > oldLineNumber ? newLineNumber : oldLineNumber
28 | }
29 | }
30 |
31 | return 0
32 | }
33 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Models/Diff/Raw-Diff.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Raw-Diff.swift
3 | // AuroraEditor
4 | //
5 | // Created by Nanashi Li on 2022/08/29.
6 | // Copyright © 2022 Aurora Company. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public enum DiffHunkExpansionType: String {
12 | /// The hunk header cannot be expanded at all.
13 | case none = "None"
14 |
15 | /// The hunk header can be expanded up exclusively. Only the first hunk can be
16 | /// expanded up exclusively.
17 | case up = "Up" // swiftlint:disable:this identifier_name
18 |
19 | /// The hunk header can be expanded down exclusively. Only the last hunk (if
20 | /// it's the dummy hunk with only one line) can be expanded down exclusively.
21 | case down = "Down"
22 |
23 | /// The hunk header can be expanded both up and down.
24 | case both = "Both"
25 |
26 | /// The hunk header represents a short gap that, when expanded, will
27 | /// result in merging this hunk and the hunk above.
28 | case short = "Short"
29 | }
30 |
31 | /// Each diff is made up of a number of hunks
32 | public class DiffHunk {
33 | var header: DiffHunkHeader
34 | var lines: [DiffLine]
35 | var unifiedDiffStart: Int
36 | var unifiedDiffEnd: Int
37 | var expansionType: DiffHunkExpansionType
38 |
39 | init(header: DiffHunkHeader,
40 | lines: [DiffLine],
41 | unifiedDiffStart: Int,
42 | unifiedDiffEnd: Int,
43 | expansionType: DiffHunkExpansionType) {
44 | self.header = header
45 | self.lines = lines
46 | self.unifiedDiffStart = unifiedDiffStart
47 | self.unifiedDiffEnd = unifiedDiffEnd
48 | self.expansionType = expansionType
49 | }
50 | }
51 |
52 | class DiffHunkHeader {
53 | var oldStartLine: Int
54 | var oldLineCount: Int
55 | var newStartLine: Int
56 | var newLineCount: Int
57 |
58 | init(oldStartLine: Int, oldLineCount: Int, newStartLine: Int, newLineCount: Int) {
59 | self.oldStartLine = oldStartLine
60 | self.oldLineCount = oldLineCount
61 | self.newStartLine = newStartLine
62 | self.newLineCount = newLineCount
63 | }
64 |
65 | public func toDiffLineRepresentation() -> String {
66 | return "@@ -\(self.oldStartLine),\(self.oldLineCount) +\(self.newStartLine),\(self.newLineCount) @@"
67 | }
68 | }
69 |
70 | public class IRawDiff {
71 | /// The plain text contents of the diff header. This contains
72 | /// everything from the start of the diff up until the first
73 | /// hunk header starts. Note that this does not include a trailing
74 | /// newline.
75 | var header: String
76 |
77 | /// The plain text contents of the diff. This contains everything
78 | /// after the diff header until the last character in the diff.
79 | ///
80 | /// Note that this does not include a trailing newline nor does
81 | /// it include diff 'no newline at end of file' comments. For
82 | /// no-newline information, consult the DiffLine noTrailingNewLine
83 | /// property.
84 | var contents: String
85 |
86 | /// Each hunk in the diff with information about start, and end
87 | /// positions, lines and line statuses.
88 | var hunks: [DiffHunk]
89 |
90 | /// Whether or not the unified diff indicates that the contents
91 | /// could not be diffed due to one of the versions being binary.
92 | var isBinary: Bool
93 |
94 | /// The largest line number in the diff
95 | var maxLineNumber: Int
96 |
97 | /// Whether or not the diff has invisible bidi characters
98 | var hasHiddenBidiChars: Bool
99 |
100 | init(header: String,
101 | contents: String,
102 | hunks: [DiffHunk],
103 | isBinary: Bool,
104 | maxLineNumber: Int,
105 | hasHiddenBidiChars: Bool) {
106 | self.header = header
107 | self.contents = contents
108 | self.hunks = hunks
109 | self.isBinary = isBinary
110 | self.maxLineNumber = maxLineNumber
111 | self.hasHiddenBidiChars = hasHiddenBidiChars
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Models/Files/AppFileStatus.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppFileStatus.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/10/31.
6 | //
7 |
8 | import Foundation
9 |
10 | public enum GitStatusEntry: String, Codable {
11 | case modified = "M"
12 | case added = "A"
13 | case deleted = "D"
14 | case renamed = "R"
15 | case copied = "C"
16 | case unchanged = "."
17 | case untracked = "?"
18 | case ignored = "!"
19 | case updatedButUnmerged = "U"
20 | }
21 |
22 | public enum AppFileStatusKind: String, Codable {
23 | case new = "New"
24 | case modified = "Modified"
25 | case deleted = "Deleted"
26 | case copied = "Copied"
27 | case renamed = "Renamed"
28 | case conflicted = "Conflicted"
29 | case untracked = "Untracked"
30 | }
31 |
32 | public struct SubmoduleStatus: Codable {
33 | let commitChanged: Bool
34 | let modifiedChanges: Bool
35 | let untrackedChanges: Bool
36 | }
37 |
38 | public struct PlainFileStatus: AppFileStatus, Codable {
39 | public var kind: AppFileStatusKind
40 | public var submoduleStatus: SubmoduleStatus?
41 | }
42 |
43 | public struct CopiedOrRenamedFileStatus: AppFileStatus, Codable {
44 | public var kind: AppFileStatusKind
45 | let oldPath: String
46 | public var submoduleStatus: SubmoduleStatus?
47 | }
48 |
49 | // MARK: - Conflicted
50 | public protocol ConflictedFileStatus: AppFileStatus {}
51 |
52 | public struct ConflictsWithMarkers: ConflictedFileStatus, Codable {
53 | public var kind: AppFileStatusKind
54 | let entry: TextConflictEntry
55 | let conflictMarkerCount: Int
56 | public var submoduleStatus: SubmoduleStatus?
57 | }
58 |
59 | public struct ManualConflict: ConflictedFileStatus, Codable {
60 | public var kind: AppFileStatusKind
61 | let entry: ManualConflictEntry
62 | public var submoduleStatus: SubmoduleStatus?
63 | }
64 |
65 | public func isConflictedFileStatus(_ appFileStatus: AppFileStatus) -> Bool {
66 | return appFileStatus.kind == .conflicted
67 | }
68 |
69 | public func isConflictWithMarkers(_ conflictedFileStatus: ConflictedFileStatus) -> Bool {
70 | return conflictedFileStatus is ConflictsWithMarkers
71 | }
72 |
73 | public func isManualConflict(_ conflictedFileStatus: ConflictedFileStatus) -> Bool {
74 | return conflictedFileStatus is ManualConflict
75 | }
76 |
77 | public struct UntrackedFileStatus: AppFileStatus, Codable {
78 | public var kind: AppFileStatusKind
79 | public var submoduleStatus: SubmoduleStatus?
80 | }
81 |
82 | public protocol AppFileStatus: Codable {
83 | var kind: AppFileStatusKind { get set }
84 | var submoduleStatus: SubmoduleStatus? { get set }
85 | }
86 |
87 | public enum UnmergedEntrySummary: String, Codable {
88 | case AddedByUs = "added-by-us"
89 | case DeletedByUs = "deleted-by-us"
90 | case AddedByThem = "added-by-them"
91 | case DeletedByThem = "deleted-by-them"
92 | case BothDeleted = "both-deleted"
93 | case BothAdded = "both-added"
94 | case BothModified = "both-modified"
95 | }
96 |
97 | public struct ManualConflictDetails: Codable {
98 | let submoduleStatus: SubmoduleStatus?
99 | let action: UnmergedEntrySummary
100 | let us: GitStatusEntry
101 | let them: GitStatusEntry
102 | }
103 |
104 | public struct TextConflictDetails: Codable {
105 | let action: UnmergedEntrySummary
106 | let us: GitStatusEntry
107 | let them: GitStatusEntry
108 | }
109 |
110 | // MARK: - Entry Conformities
111 |
112 | protocol FileEntry {
113 | var kind: String { get }
114 | var submoduleStatus: SubmoduleStatus? { get }
115 | }
116 |
117 | protocol UnmergedEntry {}
118 |
119 | public struct TextConflictEntry: Codable, FileEntry, UnmergedEntry {
120 | let kind: String = "conflicted"
121 | let submoduleStatus: SubmoduleStatus?
122 | let details: TextConflictDetails
123 | }
124 |
125 | public struct ManualConflictEntry: Codable, FileEntry, UnmergedEntry {
126 | let kind: String = "conflicted"
127 | let submoduleStatus: SubmoduleStatus?
128 | let details: ManualConflictDetails
129 | }
130 |
131 | struct UntrackedEntry: FileEntry {
132 | let kind: String = "untracked"
133 | let submoduleStatus: SubmoduleStatus?
134 | }
135 |
136 | struct RenamedOrCopiedEntry: FileEntry {
137 | enum RenamedOrCopiedEntryType: String {
138 | case renamed
139 | case copied
140 | }
141 |
142 | let kind: String
143 | let index: GitStatusEntry?
144 | let workingTree: GitStatusEntry?
145 | let submoduleStatus: SubmoduleStatus?
146 |
147 | init(kind: RenamedOrCopiedEntryType,
148 | index: GitStatusEntry?,
149 | workingTree: GitStatusEntry?,
150 | submoduleStatus: SubmoduleStatus?) {
151 | self.kind = kind.rawValue
152 | self.index = index
153 | self.workingTree = workingTree
154 | self.submoduleStatus = submoduleStatus
155 | }
156 |
157 | }
158 |
159 | struct OrdinaryEntry: FileEntry {
160 |
161 | enum OrdinaryEntryType {
162 | case added
163 | case modified
164 | case deleted
165 | }
166 |
167 | let kind: String = "ordinary"
168 | let type: OrdinaryEntryType
169 | let index: GitStatusEntry?
170 | let workingTree: GitStatusEntry?
171 | let submoduleStatus: SubmoduleStatus?
172 | }
173 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Models/Files/CommittedFileChange.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CommittedFileChange.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/10/31.
6 | //
7 |
8 | import Foundation
9 |
10 | class CommittedFileChange: FileChange {
11 | let commitish: String
12 | let parentCommitish: String
13 |
14 | init(path: String,
15 | status: AppFileStatus,
16 | commitish: String,
17 | parentCommitish: String) {
18 | self.commitish = commitish
19 | self.parentCommitish = parentCommitish
20 | super.init(path: path, status: status)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Models/Files/FileChange.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FileChange.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/10/31.
6 | //
7 |
8 | import Foundation
9 |
10 | open class FileChange {
11 | let id: String
12 | let path: String
13 | let status: AppFileStatus?
14 |
15 | public init(path: String,
16 | status: AppFileStatus?) {
17 | self.path = path
18 | self.status = status
19 |
20 | var fileId: String = ""
21 |
22 | // Generate a unique identifier based on the status and path.
23 | if let plainStatus = status as? PlainFileStatus {
24 | fileId = "plain+\(plainStatus.kind)+\(path)"
25 | } else if let copiedOrRenamedStatus = status as? CopiedOrRenamedFileStatus {
26 | fileId = "copiedOrRenamed+\(copiedOrRenamedStatus.oldPath)->\(path)"
27 | } else if status is ConflictsWithMarkers {
28 | fileId = "conflictsWithMarkers+\(path)"
29 | } else if status is ManualConflict {
30 | fileId = "manualConflict+\(path)"
31 | } else if status is UntrackedFileStatus {
32 | fileId = "untracked+\(path)"
33 | } else {
34 | print("Unknown AppFileStatus type")
35 | }
36 |
37 | self.id = fileId
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Models/Files/WorkingDirectoryFileChange.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WorkingDirectoryFileChange.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/11/13.
6 | //
7 |
8 | import Foundation
9 |
10 | open class WorkingDirectoryFileChange: FileChange {
11 | let selection: DiffSelection
12 |
13 | public init(path: String,
14 | status: AppFileStatus?,
15 | selection: DiffSelection) {
16 | self.selection = selection
17 | super.init(path: path, status: status)
18 | }
19 |
20 | func withIncludeAll(include: Bool) -> WorkingDirectoryFileChange {
21 | let newSelection = include ? selection.withSelectAll() : selection.withSelectNone()
22 | return withSelection(newSelection)
23 | }
24 |
25 | func withSelection(_ selection: DiffSelection) -> WorkingDirectoryFileChange {
26 | return WorkingDirectoryFileChange(path: self.path, status: self.status, selection: selection)
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Models/GitCommit.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Commit.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 | func shortenSHA(_ sha: String) -> String {
13 | return String(sha.prefix(9))
14 | }
15 |
16 | /// Grouping of information required to create a commit
17 | public protocol ICommitContext {
18 | /// The summary of the commit message (required)
19 | var summary: String? { get }
20 | /// Additional details for the commit message (optional)
21 | var description: String? { get }
22 | /// Whether or not it should amend the last commit (optional, default: false)
23 | var amend: Bool? { get }
24 | /// An optional array of commit trailers (for example Co-Authored-By trailers)
25 | /// which will be appended to the commit message in accordance with the Git trailer configuration.
26 | var trailers: [Trailer]? { get }
27 | }
28 |
29 | public struct CommitContext: ICommitContext {
30 | public var summary: String?
31 | public var description: String?
32 | public var amend: Bool?
33 | public var trailers: [Trailer]?
34 |
35 | public init(summary: String?,
36 | description: String?,
37 | amend: Bool?,
38 | trailers: [Trailer]?) {
39 | self.summary = summary
40 | self.description = description
41 | self.amend = amend
42 | self.trailers = trailers
43 | }
44 | }
45 |
46 | /// Extract any Co-Authored-By trailers from an array of arbitrary
47 | /// trailers.
48 | public func extractCoAuthors(trailers: [Trailer]) -> [GitAuthor] {
49 | var coAuthors: [GitAuthor] = []
50 |
51 | for trailer in trailers where InterpretTrailers().isCoAuthoredByTrailer(trailer: trailer) {
52 | let author = GitAuthor(name: nil, email: nil).parse(nameAddr: trailer.value)
53 | if author != nil {
54 | coAuthors.append(author!)
55 | }
56 | }
57 |
58 | return coAuthors
59 | }
60 |
61 | /// A git commit.
62 | public struct Commit: Codable, Equatable, Identifiable, Hashable {
63 | public var id = UUID()
64 |
65 | /// A list of co-authors parsed from the commit message
66 | /// trailers.
67 | public var coAuthors: [GitAuthor]?
68 | /// The commit body after removing coauthors
69 | public var bodyNoCoAuthors: String?
70 | /// A value indicating whether the author and the committer
71 | /// are the same person.
72 | public var authoredByCommitter: Bool
73 | /// Whether or not the commit is a merge commit (i.e. has at least 2 parents)
74 | public var isMergeCommit: Bool
75 |
76 | public var sha: String
77 | public var shortSha: String
78 | public var summary: String
79 | public var body: String
80 | public var author: CommitIdentity
81 | public var committer: CommitIdentity
82 | public var parentSHAs: [String]
83 | public var trailers: [Trailer]
84 | public var tags: [String]
85 |
86 | public init(sha: String,
87 | shortSha: String,
88 | summary: String,
89 | body: String,
90 | author: CommitIdentity,
91 | commiter: CommitIdentity,
92 | parentShas: [String],
93 | trailers: [Trailer],
94 | tags: [String]) {
95 | self.sha = sha
96 | self.shortSha = shortSha
97 | self.summary = summary
98 | self.body = body
99 | self.author = author
100 | self.committer = commiter
101 | self.parentSHAs = parentShas
102 | self.trailers = trailers
103 | self.tags = tags
104 |
105 | self.coAuthors = extractCoAuthors(trailers: trailers)
106 | self.authoredByCommitter = (author.name == committer.name && author.email == committer.email)
107 | self.bodyNoCoAuthors = InterpretTrailers().trimCoAuthorsTrailers(trailers: trailers, body: body)
108 | self.isMergeCommit = parentShas.count > 1
109 | }
110 |
111 | public init(sha: String,
112 | summary: String) {
113 | self.sha = sha
114 | self.summary = summary
115 |
116 | self.shortSha = ""
117 | self.body = ""
118 | self.author = CommitIdentity(name: "",
119 | email: "",
120 | date: Date())
121 | self.committer = CommitIdentity(name: "",
122 | email: "",
123 | date: Date())
124 | self.parentSHAs = []
125 | self.trailers = []
126 | self.tags = []
127 |
128 | self.coAuthors = nil
129 | self.authoredByCommitter = false
130 | self.bodyNoCoAuthors = nil
131 | self.isMergeCommit = false
132 | }
133 |
134 | public static func == (lhs: Commit, rhs: Commit) -> Bool {
135 | return lhs.sha == rhs.sha
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Models/GitFileItem.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GitFileItem.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2022/10/05.
6 | //
7 |
8 | import Foundation
9 |
10 | @available(macOS, deprecated, message: "Use `FileChange` instead")
11 | public protocol GitFileItem: Codable {
12 |
13 | var gitStatus: GitType? { get set }
14 |
15 | /// Returns the URL of the ``FileSystemClient/FileSystemClient/FileItem``
16 | var url: URL { get set }
17 | }
18 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Models/GitType.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GitType.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2022/05/20.
6 | //
7 |
8 | import Foundation
9 |
10 | // Used to determine the git type
11 | @available(macOS, deprecated, message: "We use the new AppFileStatus protocol")
12 | public enum GitType: String, Codable {
13 | case modified = "M"
14 | case unknown = "??"
15 | case fileTypeChange = "T"
16 | case added = "A"
17 | case deleted = "D"
18 | case renamed = "R"
19 | case copied = "C"
20 | case updatedUnmerged = "U"
21 | case ignored = "!"
22 | case unchanged = "."
23 |
24 | public var description: String {
25 | switch self {
26 | case .modified: return "M"
27 | case .unknown: return "?"
28 | case .fileTypeChange: return "T"
29 | case .added: return "A"
30 | case .deleted: return "D"
31 | case .renamed: return "R"
32 | case .copied: return "C"
33 | case .updatedUnmerged: return "U"
34 | case .ignored: return "!"
35 | case .unchanged: return "."
36 | }
37 | }
38 | }
39 |
40 | /// The enum representation of a Git file change in Aurora Editor.
41 | @available(macOS, deprecated, message: "We use the new AppFileStatus protocol")
42 | enum FileStatusKind: String {
43 | case new = "New"
44 | case modified = "Modified"
45 | case deleted = "Deleted"
46 | case copied = "Copied"
47 | case renamed = "Renamed"
48 | case conflicted = "Conflicted"
49 | case untracked = "Untracked"
50 | }
51 |
52 | /// The porcelain status for an unmerged entry
53 | @available(macOS, deprecated, message: "We use the new AppFileStatus protocol")
54 | func untrackedEntry() -> String {
55 | return "untracked"
56 | }
57 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Models/IGitAccount.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IGitAccount.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/10/31.
6 | //
7 |
8 | import Foundation
9 |
10 | /**
11 | * An account which can be used to potentially authenticate with a git server.
12 | */
13 | public struct IGitAccount {
14 |
15 | /** The login/username to authenticate with. */
16 | let login: String
17 |
18 | /** The endpoint with which the user is authenticating. */
19 | let endpoint: String
20 | }
21 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Models/IRemote.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IRemote.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 | private var forkedRemotePrefix = "aurora-editor-"
13 |
14 | public func forkPullRequestRemoteName(remoteName: String) -> String {
15 | return "\(forkedRemotePrefix)\(remoteName)"
16 | }
17 |
18 | public protocol IRemote {
19 | var name: String { get }
20 | var url: String { get }
21 | }
22 |
23 | public struct GitRemote: IRemote, Hashable {
24 | public var id: String { self.name }
25 | public var name: String
26 | public var url: String
27 |
28 | init(name: String, url: String) {
29 | self.name = name
30 | self.url = url
31 | }
32 |
33 | public static func == (lhs: GitRemote, rhs: GitRemote) -> Bool {
34 | return lhs.name == rhs.name
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Models/ManualConflictResolution.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ManualConflictResolution.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 | // NOTE: These strings have semantic value, they're passed directly
13 | // as `--ours` and `--theirs` to git checkout. Please be careful
14 | // when modifying this type.
15 | public enum ManualConflictResolution: String {
16 | case theirs = "theirs" // swiftlint:disable:this redundant_string_enum_value
17 | case ours = "ours" // swiftlint:disable:this redundant_string_enum_value
18 | }
19 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Base/Models/Stash-Entry.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Stash-Entry.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 | protocol IStashEntry {
13 | /// The fully qualified name of the entry i.e., `refs/stash@{0}`
14 | var name: String? { get }
15 | /// The name of the branch at the time the entry was created.
16 | var branchName: String? { get }
17 | /// The SHA of the commit object created as a result of stashing.
18 | var stashSha: String? { get }
19 | /// The list of files this stash touches
20 | var files: GitFileItem? { get }
21 |
22 | var tree: String? { get }
23 | var parents: [String]? { get }
24 | }
25 |
26 | class StashEntry: IStashEntry {
27 | var name: String?
28 | var branchName: String?
29 | var stashSha: String?
30 | var files: GitFileItem?
31 | var tree: String?
32 | var parents: [String]?
33 |
34 | init(name: String?,
35 | branchName: String?,
36 | stashSha: String?,
37 | files: GitFileItem?,
38 | tree: String?,
39 | parents: [String]?) {
40 | self.branchName = branchName
41 | self.name = name
42 | self.stashSha = stashSha
43 | self.files = files
44 | self.tree = tree
45 | self.parents = parents
46 | }
47 | }
48 |
49 | /// Whether file changes for a stash entry are loaded or not
50 | enum StashedChangesLoadStates: String {
51 | case notLoaded = "NotLoaded"
52 | case loading = "Loading"
53 | case loaded = "Loaded"
54 | }
55 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Errors/IndexError.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IndexError.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/11/16.
6 | //
7 |
8 | import Foundation
9 |
10 | enum IndexError: Error {
11 | case unknownIndex(String)
12 | case noRenameIndex(String)
13 | case invalidStatus(String)
14 | }
15 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Errors/NetworkingError.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NetworkingError.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/11/26.
6 | //
7 |
8 | import Foundation
9 |
10 | public enum NetworkingError: Error {
11 | case invalidURL
12 | case noData
13 | case invalidResponse
14 | case serverError(statusCode: Int, data: Data)
15 | case encodingFailed(Error)
16 | case customError(message: String)
17 |
18 | public var localizedDescription: String {
19 | switch self {
20 | case .invalidURL:
21 | return "The URL provided was invalid."
22 | case .noData:
23 | return "No data was received from the server."
24 | case .invalidResponse:
25 | return "The response received from the server was invalid."
26 | case .serverError(let statusCode, let data):
27 | let errorMessage = String(data: data, encoding: .utf8) ?? "Unknown server error"
28 | return "Server error with status code \(statusCode): \(errorMessage)"
29 | case .encodingFailed(let error):
30 | return "Failed to encode parameters: \(error.localizedDescription)"
31 | case .customError(let message):
32 | return message
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Errors/ShellErrors.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/09/25.
6 | //
7 |
8 | import Foundation
9 |
10 | enum ShellErrors: String, Error {
11 | // swiftlint:disable:next line_length
12 | case failedToInitializeRepository = "An error occurred while attempting to initialize the Git repository. Possible reasons for this failure include:\n\n1. The specified directory does not exist or is inaccessible.\n2. Git is not installed on your system, or it is not in the system's PATH.\n3. There may be a conflict with an existing Git repository in the specified directory.\n4. The Git initialization command encountered an unexpected issue."
13 | // swiftlint:disable:next line_length
14 | case failedToInstallLFS = "An error occurred while attempting to install Git Large File Storage (LFS). Possible reasons for this failure include:\n\n1. There may be a network issue preventing the installation of Git LFS.\n2. The Git LFS installation command encountered an unexpected issue."
15 | }
16 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Services/API/BitBucket/Interfaces/Repo/IBitBucketAPIPullRequest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IBitBucketAPIPullRequest.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/10/30.
6 | //
7 |
8 | import Foundation
9 |
10 | struct IBitBucketAPIPullRequest: Codable {
11 | public let id: Int
12 | public let title: String
13 | public let created_on: String
14 | public let updated_on: String
15 | public let author: IAPIIdentity
16 | public let summary: IBitbucketRenderedBody
17 | public let state: String
18 | }
19 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Services/API/BitBucket/Interfaces/Repo/IBitbucketComment.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IBitbucketComment.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/10/31.
6 | //
7 |
8 | import Foundation
9 |
10 | /**
11 | Represents both issue comments and PR review comments.
12 | */
13 | public struct IBitbucketComment: Codable {
14 | public let id: Int
15 | public let body: String
16 | public let html_url: String
17 | public let user: IAPIIdentity
18 | public let created_at: String
19 | }
20 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Services/API/BitBucket/Interfaces/Repo/IBitbucketRendered.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IBitbucketRendered.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/10/31.
6 | //
7 |
8 | import Foundation
9 |
10 | struct IBitbucketRendered {
11 | var description: IBitbucketRenderedBody
12 | }
13 |
14 | struct IBitbucketRenderedBody: Codable {
15 | var raw: String
16 | var markup: String
17 | var html: String
18 | }
19 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Services/API/GitHub/AuthorizationResponse.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AuthorizationResponse.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/09/24.
6 | //
7 |
8 | import Foundation
9 |
10 | /// Struct representing an authorization response.
11 | struct AuthorizationResponse {
12 | /// The kind of authorization response.
13 | let kind: AuthorizationResponseKind
14 |
15 | /// The token associated with successful authorization.
16 | let token: String?
17 |
18 | /// The HTTP response associated with failed authorization.
19 | let response: String?
20 |
21 | /// The type of authentication mode required for two-factor authentication.
22 | let type: AuthenticationMode?
23 | }
24 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Services/API/GitHub/AuthorizationResponseKind.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AuthorizationResponseKind.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/09/24.
6 | //
7 |
8 | /// Enum representing different kinds of authorization responses.
9 | enum AuthorizationResponseKind {
10 | /// The authorization was successful.
11 | case authorized
12 |
13 | /// The authorization failed.
14 | case failed
15 |
16 | /// Two-factor authentication is required.
17 | case twoFactorAuthenticationRequired
18 |
19 | /// User verification is required.
20 | case userRequiresVerification
21 |
22 | /// Personal access token is blocked.
23 | case personalAccessTokenBlocked
24 |
25 | /// An error occurred during authorization.
26 | case error
27 |
28 | /// The enterprise is too old for the authorization.
29 | case enterpriseTooOld
30 |
31 | /// Web authentication flow is required.
32 | ///
33 | /// The API has indicated that the user is required to go through
34 | /// the web authentication flow.
35 | case webFlowRequired
36 | }
37 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Services/API/GitHub/GithubNetworkingConstants.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NetworkingConstant.swift
3 | // AuroraEditor
4 | //
5 | // Created by Nanashi Li on 2022/09/13.
6 | // Copyright © 2022 Aurora Company. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | // swiftlint:disable:next convenience_type
12 | struct GithubNetworkingConstants {
13 | static var baseURL: String = "https://api.github.com/"
14 |
15 | // GitHub Actions
16 |
17 | // MARK: Workflows
18 | static func workflows(_ owner: String, _ repo: String) -> String {
19 | return "repos/\(owner)/\(repo)/actions/workflows"
20 | }
21 |
22 | static func workflow(_ owner: String,
23 | _ repo: String,
24 | workflowId: String) -> String {
25 | return "repos/\(owner)/\(repo)/actions/workflows/\(workflowId)"
26 | }
27 |
28 | static func workflowRuns(_ owner: String,
29 | _ repo: String,
30 | workflowId: String) -> String {
31 | return "repos/\(owner)/\(repo)/actions/workflows/\(workflowId)/runs"
32 | }
33 |
34 | // MARK: Workflow Runs
35 | static func reRunWorkflow(_ owner: String,
36 | _ repo: String,
37 | runId: String) -> String {
38 | return "repos/\(owner)/\(repo)/actions/runs/\(runId)/rerun"
39 | }
40 |
41 | static func cancelWorkflow(_ owner: String,
42 | _ repo: String,
43 | runId: String) -> String {
44 | return "repos/\(owner)/\(repo)/actions/runs/\(runId)/cancel"
45 | }
46 |
47 | // MARK: Workflow Jobs
48 | static func reRunJob(_ owner: String,
49 | _ repo: String,
50 | jobId: String) -> String {
51 | return "repos/\(owner)/\(repo)/actions/jobs/\(jobId)/rerun"
52 | }
53 |
54 | static func workflowJob(_ owner: String,
55 | _ repo: String,
56 | jobId: String) -> String {
57 | return "repos/\(owner)/\(repo)/actions/jobs/\(jobId)"
58 | }
59 |
60 | static func workflowJobs(_ owner: String,
61 | _ repo: String,
62 | runId: String) -> String {
63 | return "repos/\(owner)/\(repo)/actions/runs/\(runId)/jobs"
64 | }
65 |
66 | static func downloadWorkflowJobLog(_ owner: String,
67 | _ repo: String,
68 | jobId: String) -> String {
69 | return "repos/\(owner)/\(repo)/actions/jobs/\(jobId)/logs"
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Services/API/GitHub/Interfaces/Account/IAPIEmail.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IAPIEmail.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/09/24.
6 | //
7 |
8 | import Foundation
9 |
10 | /// `null` can be returned by the API for legacy reasons. A non-null value is
11 | /// set for the primary email address currently, but in the future visibility
12 | /// may be defined for each email address.
13 | public enum EmailVisibility: String, Codable {
14 | case `public` = "public"
15 | case `private` = "private"
16 | case `null` = ""
17 | }
18 |
19 | /// Information about a user's email as returned by the GitHub API.
20 | public struct IAPIEmail: Codable {
21 | let email: String
22 | let verified: Bool
23 | let primary: Bool
24 | let visibility: EmailVisibility
25 |
26 | public init(
27 | email: String,
28 | verified: Bool,
29 | primary: Bool,
30 | visibility: EmailVisibility
31 | ) {
32 | self.email = email
33 | self.verified = verified
34 | self.primary = primary
35 | self.visibility = visibility
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Services/API/GitHub/Interfaces/Account/IAPIFullIdentity.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IAPIFullIdentity.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/09/24.
6 | //
7 |
8 | import Foundation
9 |
10 | /**
11 | * Complete identity details returned in some situations by the GitHub API.
12 | *
13 | * If you are not sure what is returned as part of an API response, you should
14 | * use `IAPIIdentity` as that contains the known subset of an identity and does
15 | * not cover scenarios where privacy settings of a user control what information
16 | * is returned.
17 | */
18 | struct IAPIFullIdentity: Codable {
19 | let id: Int
20 | let htmlUrl: String
21 | let login: String
22 | let avatarUrl: String
23 |
24 | /**
25 | * The user's real name or null if the user hasn't provided
26 | * a real name for their public profile.
27 | */
28 | let name: String?
29 |
30 | /**
31 | * The email address for this user or null if the user has not
32 | * specified a public email address in their profile.
33 | */
34 | let email: String?
35 | let type: GitHubAccountType
36 | let plan: Plan?
37 |
38 | struct Plan: Codable {
39 | let name: String
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Services/API/GitHub/Interfaces/Account/IAPIIdentity.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IAPIIdentity.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/09/24.
6 | //
7 |
8 | import Foundation
9 |
10 | /**
11 | Minimum subset of an identity returned by the GitHub API.
12 | */
13 | public struct IAPIIdentity: Codable {
14 | public let id: Int
15 | public let login: String
16 | public let avatar_url: String
17 | public let html_url: String
18 | public let type: GitHubAccountType
19 | }
20 |
21 | /**
22 | Enumeration to represent the type of GitHub account.
23 | */
24 | public enum GitHubAccountType: String, Codable {
25 | case user = "User"
26 | case organization = "Organization"
27 | }
28 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Services/API/GitHub/Interfaces/Account/IAPIOrganization.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IAPIOrganization.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/09/24.
6 | //
7 |
8 | import Foundation
9 |
10 | /**
11 | * Entity returned by the `/user/orgs` endpoint.
12 | *
13 | * Because this is specific to one endpoint it omits the `type` member from
14 | * `IAPIIdentity` that callers might expect.
15 | */
16 | struct IAPIOrganization: Codable {
17 | let id: Int
18 | let url: String
19 | let login: String
20 | let avatarUrl: String
21 | }
22 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Services/API/GitHub/Interfaces/Repo/Branch/IAPIBranch.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IAPIBranch.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/09/24.
6 | //
7 |
8 | /**
9 | Branch information returned by the GitHub API.
10 | */
11 | public struct IAPIBranch: Codable {
12 | /**
13 | The name of the branch stored on the remote.
14 |
15 | NOTE: This is NOT a fully-qualified ref (i.e., `refs/heads/main`).
16 | */
17 | public let name: String
18 |
19 | /**
20 | Branch protection settings:
21 |
22 | - `true` indicates that the branch is protected in some way.
23 | - `false` indicates no branch protection set.
24 | */
25 | public let protected: Bool
26 | }
27 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Services/API/GitHub/Interfaces/Repo/IAPIFullRepository.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IAPIFullRepository.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/09/24.
6 | //
7 |
8 | import Foundation
9 |
10 | struct IAPIFullRepository: Codable {
11 |
12 | /**
13 | * The parent repository of a fork.
14 | *
15 | * HACK: BEWARE: This is defined as `parent: IAPIRepository | undefined`
16 | * rather than `parent?: ...` even though the parent property is actually
17 | * optional in the API response. So we're lying a bit to the type system
18 | * here saying that this will be present but the only time the difference
19 | * between omission and explicit undefined matters is when using constructs
20 | * like `x in y` or `y.hasOwnProperty('x')` which we do very rarely.
21 | *
22 | * Without at least one non-optional type in this interface TypeScript will
23 | * happily let us pass an IAPIRepository in place of an IAPIFullRepository.
24 | */
25 | let parent: IAPIRepository?
26 | let cloneUrl: String
27 | let sshUrl: String
28 | let htmlUrl: String
29 | let name: String
30 | let owner: IAPIIdentity
31 | let isPrivate: Bool
32 | let isFork: Bool
33 | let defaultBranch: String
34 | let pushedAt: String
35 | let hasIssues: Bool
36 | let isArchived: Bool
37 |
38 | /**
39 | * The high-level permissions that the currently authenticated
40 | * user enjoys for the repository. Undefined if the API call
41 | * was made without an authenticated user or if the repository
42 | * isn't the primarily requested one (i.e. if this is the parent
43 | * repository of the requested repository)
44 | *
45 | * The permissions hash will also be omitted when the repository
46 | * information is embedded within another object such as a pull
47 | * request (base.repo or head.repo).
48 | *
49 | * In other words, the only time when the permissions property
50 | * will be present is when explicitly fetching the repository
51 | * through the `/repos/user/name` endpoint or similar.
52 | */
53 | let permissions: IAPIRepositoryPermissions?
54 | }
55 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Services/API/GitHub/Interfaces/Repo/IAPIMentionableResponse.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IAPIMentionableResponse.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2024/07/15.
6 | //
7 |
8 | public struct IAPIMentionableResponse: Codable {
9 | public let etag: String?
10 | public let users: [IAPIMentionableUser]
11 | }
12 |
13 | public struct IAPIMentionableUser: Codable {
14 | /// The username or "handle" of the user
15 | public let login: String
16 | /// The user's real name (or at least the name that the user
17 | /// has configured to be shown) or null if the user hasn't provided
18 | /// a real name for their public profile.
19 | public let name: String?
20 | /// The user's attributable email address or null if the
21 | /// user doesn't have an email address that they can be
22 | /// attributed by
23 | public let email: String
24 | /// A url to an avatar image chosen by the user
25 | public let avatar_url: String
26 | }
27 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Services/API/GitHub/Interfaces/Repo/IAPIRepository.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IAPIRepository.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/09/24.
6 | //
7 |
8 | import Foundation
9 |
10 | /**
11 | * Information about a repository as returned by the GitHub API.
12 | */
13 | public struct IAPIRepository: Codable {
14 | let cloneUrl: String
15 | let sshUrl: String
16 | let htmlUrl: String
17 | let name: String
18 | let owner: IAPIIdentity
19 | let isPrivate: Bool
20 | let isFork: Bool
21 | let defaultBranch: String
22 | let pushedAt: String
23 | let hasIssues: Bool
24 | let isArchived: Bool
25 | }
26 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Services/API/GitHub/Interfaces/Repo/IAPIRepositoryCloneInfo.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IAPIRepositoryCloneInfo.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/09/24.
6 | //
7 |
8 | import Foundation
9 |
10 | // Define the IAPIRepositoryCloneInfo struct
11 | struct IAPIRepositoryCloneInfo {
12 |
13 | /** Canonical clone URL of the repository. */
14 | let url: String
15 |
16 | /**
17 | * Default branch of the repository, if any. This is usually either retrieved
18 | * from the API for GitHub repositories, or undefined for other repositories.
19 | */
20 | let defaultBranch: String?
21 | }
22 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Services/API/GitHub/Interfaces/Repo/IAPIRepositoryPermissions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IAPIRepositoryPermissions.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/09/24.
6 | //
7 |
8 | import Foundation
9 |
10 | /*
11 | * Information about how the user is permitted to interact with a repository.
12 | */
13 | struct IAPIRepositoryPermissions: Codable {
14 | let admin: Bool
15 | let push: Bool
16 | let pull: Bool
17 | }
18 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Services/API/GitHub/Interfaces/Repo/Issues/IAPIIssue.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IAPIIssue.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/09/24.
6 | //
7 |
8 | import Foundation
9 |
10 | /** Information about an issue as returned by the GitHub API. */
11 | struct IAPIIssue: Codable {
12 | let number: Int
13 | let title: String
14 | let state: APIIssueState
15 | let updatedAt: String
16 | }
17 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Services/API/GitHub/Interfaces/Repo/Pull Request/IAPIComment.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IAPIComment.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/09/24.
6 | //
7 |
8 | import Foundation
9 |
10 | /**
11 | Represents both issue comments and PR review comments.
12 | */
13 | public struct IAPIComment: Codable {
14 | public let id: Int
15 | public let body: String
16 | public let html_url: String
17 | public let user: IAPIIdentity
18 | public let created_at: String
19 | }
20 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Services/API/GitHub/Interfaces/Repo/Pull Request/IAPIPullRequest.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IAPIPullRequest.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/09/24.
6 | //
7 |
8 | import Foundation
9 |
10 | /**
11 | Represents information about a pull request from the GitHub API.
12 | */
13 | public struct IAPIPullRequest: Codable {
14 | public let number: Int
15 | public let title: String
16 | public let created_at: String
17 | public let updated_at: String
18 | public let user: IAPIIdentity
19 | public let head: IAPIPullRequestRef
20 | public let base: IAPIPullRequestRef
21 | public let body: String
22 | public let state: String
23 | public let draft: Bool?
24 | }
25 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Services/API/GitHub/Interfaces/Repo/Pull Request/IAPIPullRequestRef.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IAPIPullRequestRef.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/09/24.
6 | //
7 |
8 | import Foundation
9 |
10 | /**
11 | Represents a pull request reference from the GitHub API.
12 | */
13 | public struct IAPIPullRequestRef: Codable {
14 | public let ref: String
15 | public let sha: String
16 | public let repo: IAPIRepository?
17 | }
18 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Services/API/GitHub/Interfaces/Repo/Pull Request/IAPIPullRequestReview.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/09/24.
6 | //
7 |
8 | import Foundation
9 |
10 | /**
11 | Represents a pull request review from the GitHub API.
12 | */
13 | public struct IAPIPullRequestReview: Codable {
14 | public let id: Int
15 | public let node_id: String
16 | public let user: IAPIIdentity
17 | public let body: String?
18 | public let commit_id: String
19 | public let submitted_at: String?
20 | public let state: APIPullRequestReviewState
21 | public let html_url: String
22 | }
23 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Services/API/GitHub/Interfaces/Repo/Push/IAPIPushControl.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IAPIPushControl.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/09/24.
6 | //
7 |
8 | /**
9 | A structure representing information about push control settings for a protected branch.
10 | */
11 | struct IAPIPushControl: Codable {
12 | /**
13 | * What status checks are required before merging?
14 | *
15 | * Empty array if user is admin and branch is not admin-enforced
16 | */
17 | let required_status_checks: [String]
18 |
19 | /**
20 | * How many reviews are required before merging?
21 | *
22 | * 0 if user is admin and branch is not admin-enforced
23 | */
24 | let required_approving_review_count: Int
25 |
26 | /**
27 | * Is user permitted?
28 | *
29 | * Always `true` for admins.
30 | * `true` if `Restrict who can push` is not enabled.
31 | * `true` if `Restrict who can push` is enabled and user is in list.
32 | * `false` if `Restrict who can push` is enabled and user is not in list.
33 | */
34 | let allow_actor: Bool
35 |
36 | /**
37 | * Currently unused properties
38 | */
39 | let pattern: String?
40 | let required_signatures: Bool
41 | let required_linear_history: Bool
42 | let allow_deletions: Bool
43 | let allow_force_pushes: Bool
44 | }
45 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Services/API/GitHub/Interfaces/Repo/Ruleset/IAPIRepoRule.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/09/24.
6 | //
7 |
8 | /**
9 | Repository rule information returned by the GitHub API.
10 | */
11 | public struct IAPIRepoRule: Codable {
12 | /**
13 | The ID of the ruleset this rule is configured in.
14 | */
15 | public let ruleset_id: Int
16 |
17 | /**
18 | The type of the rule.
19 | */
20 | public let type: APIRepoRuleType
21 |
22 | /**
23 | The parameters that apply to the rule if it is a metadata rule.
24 | Other rule types may have parameters, but they are not used in
25 | this app so they are ignored. Do not attempt to use this field
26 | unless you know `type` matches a metadata rule type.
27 | */
28 | public let parameters: IAPIRepoRuleMetadataParameters?
29 | }
30 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Services/API/GitHub/Interfaces/Repo/Ruleset/IAPIRepoRuleMetadataParameters.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IAPIRepoRuleMetadataParameters.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/09/24.
6 | //
7 |
8 | import Foundation
9 |
10 | /**
11 | Metadata parameters for a repo rule metadata rule.
12 | */
13 | public struct IAPIRepoRuleMetadataParameters: Codable {
14 | /**
15 | User-supplied name/description of the rule.
16 | */
17 | public let name: String?
18 |
19 | /**
20 | Whether the operator is negated. For example, if `true`
21 | and `operator` is `starts_with`, then the rule
22 | will be negated to 'does not start with'.
23 | */
24 | public let negate: Bool?
25 |
26 | /**
27 | The pattern to match against. If the operator is 'regex', then
28 | this is a regex string match. Otherwise, it is a raw string match
29 | of the type specified by `operator` with no additional parsing.
30 | */
31 | public let pattern: String?
32 |
33 | /**
34 | The type of match to use for the pattern. For example, `starts_with`
35 | means `pattern` must be at the start of the string.
36 | */
37 | public let `operator`: APIRepoRuleMetadataOperator?
38 | }
39 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Services/API/GitHub/Interfaces/Repo/Ruleset/IAPIRepoRuleset.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IAPIRepoRuleset.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/09/24.
6 | //
7 |
8 | import Foundation
9 |
10 | public enum UserCanBypass: String, Codable {
11 | case always = "always"
12 | case pullRequestOnly = "pull_requests_only"
13 | case never = "never"
14 | }
15 |
16 | /**
17 | A ruleset returned from the GitHub API's "get a ruleset for a repo" endpoint.
18 | */
19 | public struct IAPIRepoRuleset: Codable {
20 | /// The ID of the ruleset.
21 | public let id: Int
22 |
23 | /**
24 | Whether the user making the API request can bypass the ruleset.
25 |
26 | - Possible values:
27 | - `always`: The user can always bypass the ruleset.
28 | - `pull_requests_only`: The user can bypass the ruleset only for pull requests.
29 | - `never`: The user cannot bypass the ruleset.
30 | */
31 | public let current_user_can_bypass: UserCanBypass
32 | }
33 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Services/API/GitHub/Interfaces/Repo/Ruleset/IAPISlimRepoRuleset.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IAPISlimRepoRuleset.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/09/24.
6 | //
7 |
8 | import Foundation
9 |
10 | /**
11 | A ruleset returned from the GitHub API's "get all rulesets for a repo" endpoint.
12 | This endpoint returns a slimmed-down version of the full ruleset object, though
13 | only the ID is used.
14 | */
15 | struct IAPISlimRepoRuleset: Codable {
16 | /// The ID of the ruleset.
17 | let id: Int
18 | }
19 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Services/API/GitHub/Interfaces/Repo/Ruleset/IRawAPIRepoRule.swift:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Services/API/GitHub/Interfaces/Repo/Workflow/IAPICheckSuite.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IAPICheckSuite.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/09/24.
6 | //
7 |
8 | import Foundation
9 |
10 | struct IAPICheckSuite: Codable {
11 | let id: Int
12 | let rerequestable: Bool
13 | let runs_rerequestable: Bool
14 | let status: APICheckStatus
15 | let created_at: String
16 | }
17 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Services/API/GitHub/Interfaces/Repo/Workflow/IAPIRefCheckRun.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IAPIRefCheckRun.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/09/24.
6 | //
7 |
8 | import Foundation
9 |
10 | struct IAPIRefCheckRun: Codable {
11 | let id: Int
12 | let url: String
13 | let status: APICheckStatus
14 | let conclusion: APICheckConclusion?
15 | let name: String
16 | let check_suite: IAPIRefCheckRunCheckSuite
17 | let app: IAPIRefCheckRunApp
18 | let completed_at: String
19 | let started_at: String
20 | let html_url: String
21 | let pull_requests: [IAPIPullRequest]
22 | }
23 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Services/API/GitHub/Interfaces/Repo/Workflow/IAPIRefCheckRunApp.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IAPIRefCheckRunApp.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/09/24.
6 | //
7 |
8 | import Foundation
9 |
10 | struct IAPIRefCheckRunApp: Codable {
11 | let name: String
12 | }
13 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Services/API/GitHub/Interfaces/Repo/Workflow/IAPIRefCheckRunCheckSuite.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IAPIRefCheckRunCheckSuite.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/09/24.
6 | //
7 |
8 | import Foundation
9 |
10 | struct IAPIRefCheckRunCheckSuite: Codable {
11 | let id: Int
12 | }
13 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Services/API/GitHub/Interfaces/Repo/Workflow/IAPIRefCheckRunOutput.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IAPIRefCheckRunOutput.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/09/24.
6 | //
7 |
8 | import Foundation
9 |
10 | struct IAPIRefCheckRunOutput: Codable {
11 | let title: String?
12 | let summary: String?
13 | let text: String?
14 | }
15 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Services/API/GitHub/Interfaces/Repo/Workflow/IAPIRefCheckRuns.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IAPIRefCheckRuns.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/09/24.
6 | //
7 |
8 | import Foundation
9 |
10 | struct IAPIRefCheckRuns: Codable {
11 | let total_count: Int
12 | let check_runs: [IAPIRefCheckRun]
13 | }
14 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Services/API/GitHub/Interfaces/Repo/Workflow/IAPIRefStatus.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IAPIRefStatus.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/09/24.
6 | //
7 |
8 | import Foundation
9 |
10 | struct IAPIRefStatus: Codable {
11 | let state: APIRefState
12 | let total_count: Int
13 | let statuses: [IAPIRefStatusItem]
14 | }
15 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Services/API/GitHub/Interfaces/Repo/Workflow/IAPIRefStatusItem.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IAPIRefStatusItem.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/09/24.
6 | //
7 |
8 | import Foundation
9 |
10 | struct IAPIRefStatusItem: Codable {
11 | let state: APIRefState
12 | let target_url: String?
13 | let description: String
14 | let context: String
15 | let id: Int
16 | }
17 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Services/API/GitHub/Interfaces/Repo/Workflow/IAPIWorkflowJob.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IAPIWorkflowJob.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/09/24.
6 | //
7 |
8 | import Foundation
9 |
10 | struct IAPIWorkflowJob: Codable {
11 | let id: Int
12 | let name: String
13 | let status: APICheckStatus
14 | let conclusion: APICheckConclusion?
15 | let completed_at: String
16 | let started_at: String
17 | let steps: [IAPIWorkflowJobStep]
18 | let html_url: String
19 | }
20 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Services/API/GitHub/Interfaces/Repo/Workflow/IAPIWorkflowJobStep.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IAPIWorkflowJobStep.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/09/24.
6 | //
7 |
8 | import Foundation
9 |
10 | struct IAPIWorkflowJobStep: Codable {
11 | let name: String
12 | let number: Int
13 | let status: APICheckStatus
14 | let conclusion: APICheckConclusion?
15 | let completed_at: String
16 | let started_at: String
17 | let log: String
18 | }
19 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Services/API/GitHub/Interfaces/Repo/Workflow/IAPIWorkflowJobs.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IAPIWorkflowJobs.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/09/24.
6 | //
7 |
8 | import Foundation
9 |
10 | struct IAPIWorkflowJobs: Codable {
11 | let total_count: Int
12 | let jobs: [IAPIWorkflowJob]
13 | }
14 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Services/API/GitHub/Interfaces/Repo/Workflow/IAPIWorkflowRun.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IAPIWorkflowRun.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/09/24.
6 | //
7 |
8 | import Foundation
9 |
10 | struct IAPIWorkflowRun: Codable {
11 | let id: Int
12 | let workflow_id: Int
13 | let cancel_url: String
14 | let created_at: String
15 | let logs_url: String
16 | let name: String
17 | let rerun_url: String
18 | let check_suite_id: Int
19 | let event: String
20 | }
21 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Services/API/GitHub/Interfaces/Repo/Workflow/IAPIWorkflowRuns.swift:
--------------------------------------------------------------------------------
1 | //
2 | // IAPIWorkflowRuns.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/09/24.
6 | //
7 |
8 | import Foundation
9 |
10 | struct IAPIWorkflowRuns: Codable {
11 | let total_count: Int
12 | let workflow_runs: [IAPIWorkflowRun]
13 | }
14 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Services/API/GitHub/Model/Account/GithubAccount.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GithubAccount.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/09/24.
6 | //
7 |
8 | import Foundation
9 |
10 | // Define the Account class
11 | public class Account: Codable, Equatable {
12 | let login: String
13 | let endpoint: String
14 | let token: String
15 | let emails: [IAPIEmail]
16 | let avatarURL: String
17 | let id: Int
18 | let name: String
19 | let plan: String?
20 |
21 | public init(login: String,
22 | endpoint: String,
23 | token: String,
24 | emails: [IAPIEmail],
25 | avatarURL: String,
26 | id: Int,
27 | name: String,
28 | plan: String?
29 | ) {
30 | self.login = login
31 | self.endpoint = endpoint
32 | self.token = token
33 | self.emails = emails
34 | self.avatarURL = avatarURL
35 | self.id = id
36 | self.name = name
37 | self.plan = plan
38 | }
39 |
40 | func withToken(_ token: String) -> Account {
41 | return Account(login: self.login,
42 | endpoint: self.endpoint,
43 | token: token,
44 | emails: self.emails,
45 | avatarURL: self.avatarURL,
46 | id: self.id,
47 | name: self.name,
48 | plan: self.plan)
49 | }
50 |
51 | var friendlyName: String {
52 | return self.name.isEmpty ? self.login : self.name
53 | }
54 |
55 | public static func == (lhs: Account, rhs: Account) -> Bool {
56 | return lhs.endpoint == rhs.endpoint && lhs.id == rhs.id
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Services/API/GitHub/Model/Repo/Issues/APIIssueState.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/09/24.
6 | //
7 |
8 | import Foundation
9 |
10 | enum APIIssueState: String, Codable {
11 | case open
12 | case closed
13 | }
14 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Services/API/GitHub/Model/Repo/Pull Request/APIPullRequestReviewState.swift:
--------------------------------------------------------------------------------
1 | //
2 | // APIPullRequestReviewState.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/09/24.
6 | //
7 |
8 | import Foundation
9 |
10 | public enum APIPullRequestReviewState: String, Codable {
11 | case approved = "APPROVED"
12 | case dismissed = "DISMISSED"
13 | case pending = "PENDING"
14 | case commented = "COMMENTED"
15 | case changesRequested = "CHANGES_REQUESTED"
16 | }
17 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Services/API/GitHub/Model/Repo/Ruleset/APIRepoRuleMetadataOperator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // APIRepoRuleMetadataOperator.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/09/24.
6 | //
7 |
8 | import Foundation
9 |
10 | /**
11 | Enum representing different operators for metadata rule matching.
12 | */
13 | public enum APIRepoRuleMetadataOperator: String, Codable {
14 | case startsWith = "starts_with"
15 | case endsWith = "ends_with"
16 | case contains = "contains"
17 | case regex = "regex"
18 | }
19 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Services/API/GitHub/Model/Repo/Ruleset/APIRepoRuleType.swift:
--------------------------------------------------------------------------------
1 | //
2 | // APIRepoRuleType.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/09/24.
6 | //
7 |
8 | import Foundation
9 |
10 | /**
11 | Enum representing different types of repository rules that can be configured.
12 | */
13 | public enum APIRepoRuleType: String, Codable {
14 | case creation
15 | case deletion
16 | case update
17 | case required_deployments
18 | case required_signatures
19 | case required_status_checks
20 | case required_linear_history
21 | case pull_request
22 | case commit_message_pattern
23 | case commit_author_email_pattern
24 | case committer_email_pattern
25 | case branch_name_pattern
26 | case non_fast_forward
27 | }
28 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Services/API/GitHub/Model/Repo/Workflow/APICheckConclusion.swift:
--------------------------------------------------------------------------------
1 | //
2 | // APICheckConclusion.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/09/24.
6 | //
7 |
8 | import Foundation
9 |
10 | // The conclusion of a completed check run
11 | enum APICheckConclusion: String, Codable {
12 | case actionRequired = "action_required"
13 | case canceled
14 | case timedOut = "timed_out"
15 | case failure
16 | case neutral
17 | case success
18 | case skipped
19 | case stale
20 | }
21 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Services/API/GitHub/Model/Repo/Workflow/APICheckStatus.swift:
--------------------------------------------------------------------------------
1 | //
2 | // APICheckStatus.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/09/24.
6 | //
7 |
8 | import Foundation
9 |
10 | // The overall status of a check run
11 | enum APICheckStatus: String, Codable {
12 | case queued
13 | case inProgress = "in_progress"
14 | case completed
15 | }
16 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Services/API/GitHub/Model/Repo/Workflow/APIRefState.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FiAPIRefStatele.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/09/24.
6 | //
7 |
8 | import Foundation
9 |
10 | // The combined state of a ref.
11 | enum APIRefState: String, Codable {
12 | case failure
13 | case pending
14 | case success
15 | case error
16 | }
17 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Services/API/Gitlab/GitlabAPI.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GitlabAPI.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/10/29.
6 | //
7 |
8 | import Foundation
9 |
10 | struct GitlabAPI {
11 |
12 | public init() {}
13 |
14 | func createRepritory() {
15 |
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Services/API/Global/Gravatar.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Gravatar.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/09/25.
6 | //
7 |
8 | import Foundation
9 |
10 | struct Gravatar {
11 |
12 | /// Generates a Gravatar URL based on the provided email address and size.
13 | ///
14 | /// - Parameters:
15 | /// - email: The email address associated with the Gravatar.
16 | /// - size: An optional size parameter for the Gravatar image (default is 60).
17 | ///
18 | /// - Returns: A URL string representing the Gravatar image.
19 | ///
20 | /// - Example:
21 | /// ```swift
22 | /// let email = "example@example.com"
23 | /// let gravatarUrl = generateGravatarUrl(email: email, size: 80)
24 | /// ```
25 | ///
26 | /// - Note: Gravatar is a service that provides globally recognized avatars associated with email addresses.
27 | func generateGravatarUrl(email: String, size: Int = 60) -> String {
28 | let hash = email.md5(trim: true, caseSensitive: false)
29 | return "https://www.gravatar.com/avatar/\(hash)?s=\(size)"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Services/Models/Github/Actions/Jobs/Job.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Job.swift
3 | // AuroraEditor
4 | //
5 | // Created by Nanashi Li on 2022/09/13.
6 | // Copyright © 2022 Aurora Company. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public struct Job: Codable {
12 | public let totalCount: Int
13 | public let jobs: [Jobs]
14 |
15 | enum CodingKeys: String, CodingKey {
16 | case totalCount = "total_count"
17 | case jobs
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Services/Models/Github/Actions/Jobs/JobSteps.swift:
--------------------------------------------------------------------------------
1 | //
2 | // JobSteps.swift
3 | // AuroraEditor
4 | //
5 | // Created by Nanashi Li on 2022/09/13.
6 | // Copyright © 2022 Aurora Company. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public struct JobSteps: Codable {
12 | public let name: String
13 | public let status: String
14 | public let conclusion: String
15 | public let number: Int
16 | public let startedAt: String
17 | public let completedAt: String
18 |
19 | enum CodingKeys: String, CodingKey {
20 | case name
21 | case status
22 | case conclusion
23 | case number
24 | case startedAt = "started_at"
25 | case completedAt = "completed_at"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Services/Models/Github/Actions/Jobs/Jobs.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Jobs.swift
3 | // AuroraEditor
4 | //
5 | // Created by Nanashi Li on 2022/09/13.
6 | // Copyright © 2022 Aurora Company. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public struct Jobs: Codable {
12 | public let id: Int
13 | public let runId: Int
14 | public let runURL: String
15 | public let runAttempt: Int
16 | public let url: String
17 | public let htmlURL: String
18 | public let status: String
19 | public let conclusion: String
20 | public let startedAt: String
21 | public let completedAt: String
22 | public let name: String
23 | public let steps: [JobSteps]
24 | public let runnerName: String?
25 | public let runnerGroupName: String?
26 |
27 | enum CodingKeys: String, CodingKey {
28 | case id
29 | case runId = "run_id"
30 | case runURL = "run_url"
31 | case runAttempt = "run_attempt"
32 | case url
33 | case htmlURL = "html_url"
34 | case status
35 | case conclusion
36 | case startedAt = "started_at"
37 | case completedAt = "completed_at"
38 | case name
39 | case steps
40 | case runnerName = "runner_name"
41 | case runnerGroupName = "runner_group_name"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Services/Models/Github/Actions/Workflow/Workflow.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Workflow.swift
3 | // AuroraEditor
4 | //
5 | // Created by Nanashi Li on 2022/09/13.
6 | // Copyright © 2022 Aurora Company. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SwiftUI
11 |
12 | public struct Workflow: Codable, Hashable, Identifiable, Comparable {
13 | public static func < (lhs: Workflow, rhs: Workflow) -> Bool {
14 | return lhs.name < rhs.name
15 | }
16 |
17 | public let id: Int
18 | public let nodeId: String
19 | public let name: String
20 | public let path: String
21 | public let state: String
22 | public let createdAt: String
23 | public let updatedAt: String
24 | public let url: String
25 | public let htmlURL: String
26 |
27 | enum CodingKeys: String, CodingKey {
28 | case id
29 | case nodeId = "node_id"
30 | case name
31 | case path
32 | case state
33 | case createdAt = "created_at"
34 | case updatedAt = "updated_at"
35 | case url
36 | case htmlURL = "html_url"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Services/Models/Github/Actions/Workflow/WorkflowRun.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WorkflowRun.swift
3 | // AuroraEditor
4 | //
5 | // Created by Nanashi Li on 2022/09/13.
6 | // Copyright © 2022 Aurora Company. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public struct WorkflowRun: Codable {
12 | public let id: Int
13 | public let name: String
14 | public let nodeId: String
15 | public let headBranch: String
16 | public let runNumber: Int
17 | public let status: String
18 | public let conclusion: String
19 | public let workflowId: Int
20 | public let url: String
21 | public let htmlURL: String
22 | public let createdAt: String
23 | public let updatedAt: String
24 | public let headCommit: WorkflowRunCommit
25 |
26 | enum CodingKeys: String, CodingKey {
27 | case id
28 | case name
29 | case nodeId = "node_id"
30 | case headBranch = "head_branch"
31 | case runNumber = "run_number"
32 | case status
33 | case conclusion
34 | case workflowId = "workflow_id"
35 | case url
36 | case htmlURL = "html_url"
37 | case createdAt = "created_at"
38 | case updatedAt = "updated_at"
39 | case headCommit = "head_commit"
40 | }
41 | }
42 |
43 | public struct WorkflowRunCommit: Codable {
44 | public let id: String
45 | public let treeId: String
46 | public let message: String
47 | public let timestamp: String
48 | public let author: CommitAuthor
49 |
50 | enum CodingKeys: String, CodingKey {
51 | case id
52 | case treeId = "tree_id"
53 | case message
54 | case timestamp
55 | case author
56 | }
57 | }
58 |
59 | public struct CommitAuthor: Codable {
60 | public let name: String
61 | public let email: String
62 | }
63 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Services/Models/Github/Actions/Workflow/WorkflowRuns.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WorkflowRuns.swift
3 | // AuroraEditor
4 | //
5 | // Created by Nanashi Li on 2022/09/13.
6 | // Copyright © 2022 Aurora Company. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public struct WorkflowRuns: Codable {
12 | public let totalCount: Int
13 | public let workflowRuns: [WorkflowRun]?
14 |
15 | enum CodingKeys: String, CodingKey {
16 | case totalCount = "total_count"
17 | case workflowRuns = "workflow_runs"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Services/Models/Github/Actions/Workflow/Workflows.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Workflows.swift
3 | // AuroraEditor
4 | //
5 | // Created by Nanashi Li on 2022/09/13.
6 | // Copyright © 2022 Aurora Company. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public struct Workflows: Codable {
12 | public let totalCount: Int
13 | public let workflows: [Workflow]
14 |
15 | enum CodingKeys: String, CodingKey {
16 | case totalCount = "total_count"
17 | case workflows
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Services/Models/Github/Auth/2FA.swift:
--------------------------------------------------------------------------------
1 | //
2 | // 2FA.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/09/24.
6 | //
7 |
8 | let authenticatorAppWelcomeText =
9 | "Please access the two-factor authentication application on your device in order to retrieve your authentication code and complete the identity verification process."
10 | // swiftlint:disable:previous line_length
11 | let smsMessageWelcomeText =
12 | "We have recently dispatched a message to you via SMS, containing your authentication code. Kindly input this code into the provided form below to authenticate your identity."
13 | // swiftlint:disable:previous line_length
14 |
15 | enum AuthenticationMode {
16 | /*
17 | * User should authenticate via a received text message.
18 | */
19 | case sms
20 | /*
21 | * User should open TOTP mobile application and obtain code.
22 | */
23 | case app
24 | }
25 |
26 | func getWelcomeMessage(type: AuthenticationMode) -> String {
27 | return type == .sms ? smsMessageWelcomeText : authenticatorAppWelcomeText
28 | }
29 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Services/Networking/AuroraNetworkingConstants.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AuroraNetworkingConstants.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/11/26.
6 | //
7 |
8 | import Foundation
9 |
10 | public struct AuroraNetworkingConstants { // swiftlint:disable:this convenience_type
11 | public static let GithubURL = "https://api.github.com/"
12 | public static let BitbucketURL = "https://api.bitbucket.org/2.0/"
13 | }
14 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Services/Networking/AuroraNetworkingDebug.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AuroraNetworkingDebug.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/11/26.
6 | //
7 |
8 | import Foundation
9 |
10 | extension AuroraNetworking {
11 | /// Return the full networkRequestResponse
12 | /// - Returns: the full networkRequestResponse
13 | public func networkRequestResponse() -> String? {
14 | return AuroraNetworking.fullResponse
15 | }
16 |
17 | func networkLog(request: URLRequest?,
18 | session: URLSession?,
19 | response: URLResponse?,
20 | data: Data?,
21 | file: String = #file,
22 | line: Int = #line,
23 | function: String = #function) {
24 | guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode != 200 else {
25 | return
26 | }
27 |
28 | #if DEBUG
29 | print("Network debug start")
30 | networkLogRequest(request)
31 | networkLogResponse(httpResponse)
32 | networkLogData(data)
33 | print("End of network debug\n")
34 | #endif
35 | }
36 |
37 | private func networkLogRequest(_ request: URLRequest?) {
38 | guard let request = request else { return }
39 |
40 | print("URLRequest:")
41 | if let httpMethod = request.httpMethod, let url = request.url {
42 | print(" \(httpMethod) \(url)")
43 | }
44 |
45 | print("\n Headers:")
46 | if let allHTTPHeaderFields = request.allHTTPHeaderFields {
47 | for (header, content) in allHTTPHeaderFields {
48 | print(" \(header): \(content)")
49 | }
50 | }
51 |
52 | print("\n Body:")
53 | if let httpBody = request.httpBody, let body = String(data: httpBody, encoding: .utf8) {
54 | print(" \(body)")
55 | }
56 | print("\n")
57 | }
58 |
59 | private func networkLogResponse(_ response: HTTPURLResponse) {
60 | print("HTTPURLResponse:")
61 | print(" HTTP \(response.statusCode)")
62 |
63 | for (header, content) in response.allHeaderFields {
64 | print(" \(header): \(content)")
65 | }
66 | }
67 |
68 | private func networkLogData(_ data: Data?) {
69 | guard let data = data, let stringData = String(data: data, encoding: .utf8) else { return }
70 |
71 | print("\n Body:")
72 | for line in stringData.split(separator: "\n") {
73 | print(" \(line)")
74 | }
75 |
76 | do {
77 | print("\n Decoded JSON:")
78 | if let jsonObject = try JSONSerialization.jsonObject(with: data) as? [String: Any] {
79 | for (key, value) in jsonObject {
80 | print(" \(key): \(value)")
81 | }
82 | }
83 | } catch {
84 | print("JSON Decoding Error: \(error.localizedDescription)")
85 | }
86 | }
87 |
88 | private func handleNetworkError(data: Data?) -> String {
89 | guard let data = data,
90 | let errorData = String(data: data, encoding: .utf8) else {
91 | return "Unknown error occurred."
92 | }
93 |
94 | return errorData
95 | .split(separator: "\n")
96 | .map(String.init)
97 | .joined(separator: " ")
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Services/Networking/HTTPErrors.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HTTPErros.swift
3 | // AuroraEditor
4 | //
5 | // Created by Nanashi Li on 2022/09/13.
6 | // Copyright © 2022 Aurora Company. All rights reserved.
7 | //
8 |
9 | /// A enum class that has strings that can be used to check what
10 | /// type of error we got back from the lookout api.
11 | enum HTTPErrors: String, Error {
12 | case notVerified = "User is not verified"
13 | }
14 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Services/Networking/HTTPMethod.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HTTPMethod.swift
3 | // AuroraEditor
4 | //
5 | // Created by Nanashi Li on 2022/09/13.
6 | // Copyright © 2022 Aurora Company. All rights reserved.
7 | //
8 |
9 | public enum HTTPMethod: String {
10 | case GET, POST, PUT, PATCH, DELETE
11 | }
12 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Utils/BranchUtil.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/11/05.
6 | //
7 |
8 | import Foundation
9 |
10 | public struct BranchUtil {
11 |
12 | public init() {}
13 |
14 | /**
15 | Merges local and remote Git branches into a single array of Git \
16 | branches that includes branches with upstream relationships.
17 |
18 | - Parameter branches: An array of `GitBranch` instances to be merged.
19 | - Returns: An array of `GitBranch` instances containing both local and \
20 | remote branches with their respective upstream branches.
21 |
22 | This function takes an array of `GitBranch` instances and categorizes them into local and remote branches.
23 | It then creates a merged array that includes both types of branches along with their respective upstream branches.
24 | If a local branch has an associated upstream branch, it is included in the result. For remote branches,
25 | if the corresponding local branch is already added to the result, it is not added again to avoid duplication.
26 | */
27 | public func mergeRemoteAndLocalBranches(branches: [GitBranch]) -> [GitBranch] {
28 | var localBranches = [GitBranch]()
29 | var remoteBranches = [GitBranch]()
30 |
31 | for branch in branches {
32 | if branch.type == .local {
33 | localBranches.append(branch)
34 | } else if branch.type == .remote {
35 | remoteBranches.append(branch)
36 | }
37 | }
38 |
39 | var upstreamBranchesAdded = Set()
40 | var allBranchesWithUpstream = [GitBranch]()
41 |
42 | for branch in localBranches {
43 | allBranchesWithUpstream.append(branch)
44 |
45 | if let upstream = branch.upstream {
46 | upstreamBranchesAdded.insert(upstream)
47 | }
48 | }
49 |
50 | for branch in remoteBranches {
51 | // This means we already added the local branch of this remote branch, so
52 | // we don't need to add it again.
53 | if upstreamBranchesAdded.contains(branch.name) {
54 | continue
55 | }
56 |
57 | allBranchesWithUpstream.append(branch)
58 | }
59 |
60 | return allBranchesWithUpstream
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Utils/CommandError.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CommandError.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/09/24.
6 | //
7 |
8 | import Foundation
9 |
10 | enum CommandError: Error {
11 | case nonZeroExitStatus(Int) // Error with a non-zero exit status
12 | case utf8ConversionFailed // Error when UTF-8 conversion of output fails
13 | }
14 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Utils/Extensions/Date.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2022/10/05.
6 | //
7 |
8 | import Foundation
9 |
10 | public extension Date {
11 |
12 | func yearMonthDayFormat() -> String {
13 | let dateFormatter = DateFormatter()
14 | dateFormatter.dateFormat = "yyyy-MM-dd"
15 | return dateFormatter.string(from: self)
16 | }
17 |
18 | func gitDateFormat(commitDate: String) -> Date? {
19 | let dateFormatter = DateFormatter()
20 | dateFormatter.locale = Locale.current
21 | dateFormatter.dateFormat = "E MMM dd HH:mm:ss yyyy Z"
22 | return dateFormatter.date(from: commitDate)
23 | }
24 |
25 | func toGitHubIsoDateString(_ date: Date) -> String {
26 | let dateFormatter = DateFormatter()
27 | dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss'Z'"
28 | dateFormatter.timeZone = TimeZone(secondsFromGMT: 0)
29 | return dateFormatter.string(from: date)
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Utils/Extensions/FileManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FileManger.swift
3 | // AuroraEditor
4 | //
5 | // Created by Nanashi Li on 2022/08/16.
6 | // Copyright © 2022 Aurora Company. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | extension FileManager {
12 | public func directoryExistsAtPath(_ path: String) -> Bool {
13 | var isDirectory: ObjCBool = true
14 | let exists = self.fileExists(atPath: "file://\(path)", isDirectory: &isDirectory)
15 | return exists && isDirectory.boolValue
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Utils/FileUtils.swift:
--------------------------------------------------------------------------------
1 | //
2 | // FileUtils.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/11/20.
6 | //
7 |
8 | import Foundation
9 |
10 | public struct FileUtils {
11 |
12 | func writeToTempFile(content: String,
13 | tempFileName: String) async throws -> String {
14 | let tempDir = NSTemporaryDirectory()
15 | let tempFilePath = (tempDir as NSString).appendingPathComponent(tempFileName)
16 | try content.write(toFile: tempFilePath, atomically: true, encoding: .utf8)
17 | return tempFilePath
18 | }
19 |
20 | func getOldPathOrDefault(file: FileChange) -> String {
21 | if file.status?.kind == .renamed || file.status?.kind == .copied {
22 | if let file = file.status as? CopiedOrRenamedFileStatus {
23 | return file.oldPath
24 | } else {
25 | return file.path
26 | }
27 | } else {
28 | return file.path
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Utils/Helpers/DefaultBranch.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DefaultBranch.swift
3 | // AuroraEditor
4 | //
5 | // Created by Nanashi Li on 2022/08/13.
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 DefaultBranch {
13 |
14 | /// The default branch name that GitHub Desktop will use when
15 | /// initializing a new repository.
16 | private let defaultBranchInAE = "main"
17 |
18 | /// The name of the Git configuration variable which holds what
19 | /// branch name Git will use when initializing a new repository.
20 | private let defaultBranchSettingName = "init.defaultBranch"
21 |
22 | /// The branch names that Aurora Editor shows by default as radio buttons on the
23 | /// form that allows users to change default branch name.
24 | public let suggestedBranchNames: [String] = ["main, master"]
25 |
26 | public init() {}
27 |
28 | /// Returns the configured default branch when creating new repositories
29 | public func getConfiguredDefaultBranch(path: URL) throws -> String? {
30 | // TODO: Bug where global config value is not being processed correctly
31 | return try Config().getGlobalConfigValue(
32 | path: path,
33 | name: defaultBranchSettingName
34 | )
35 | }
36 |
37 | /// Returns the configured default branch when creating new repositories
38 | public func getDefaultBranch() -> String {
39 | // return try getConfiguredDefaultBranch() ?? defaultBranchInAE
40 | return defaultBranchInAE
41 | }
42 |
43 | /// Sets the configured default branch when creating new repositories.
44 | ///
45 | /// @param branchName - The default branch name to use.
46 | public func setDefaultBranch(branchName: String) throws -> String {
47 | return try Config().setGlobalConfigValue(name: defaultBranchSettingName,
48 | value: branchName)
49 | }
50 |
51 | public func findDefaultBranch(directoryURL: URL,
52 | branches: [GitBranch],
53 | defaultRemoteName: String?) throws -> GitBranch? {
54 | let remoteName: String?
55 |
56 | // TODO: Find a way to get upstream name
57 | remoteName = defaultRemoteName
58 |
59 | let remoteHead = remoteName != nil ? try Remote().getRemoteHEAD(directoryURL: directoryURL,
60 | remote: remoteName!) : nil
61 |
62 | let defaultBranchName = remoteHead ?? getDefaultBranch()
63 | let remoteRef = remoteHead != nil ? "\(remoteName!)/\(remoteHead!)" : nil
64 |
65 | var localHit: GitBranch?
66 | var localTrackingHit: GitBranch?
67 | var remoteHit: GitBranch?
68 |
69 | for branch in branches {
70 | if branch.type == .local {
71 | if branch.name == defaultBranchName {
72 | localHit = branch
73 | }
74 |
75 | if let remoteRef = remoteRef, branch.upstream == remoteRef {
76 | // Give preference to local branches that target the upstream
77 | // default branch that also match the name. In other words, if there
78 | // are two local branches which both track the origin default branch
79 | // we'll prefer a branch which is also named the same as the default
80 | // branch name.
81 | if localTrackingHit == nil || branch.name == defaultBranchName {
82 | localTrackingHit = branch
83 | }
84 | }
85 | } else if let remoteRef = remoteRef, branch.name == remoteRef {
86 | remoteHit = branch
87 | }
88 | }
89 |
90 | // When determining what the default branch is we give priority to local
91 | // branches tracking the default branch of the contribution target (think
92 | // origin) remote, then we consider local branches that are named the same
93 | // as the default branch, and finally we look for the remote branch
94 | // representing the default branch of the contribution target
95 | return localTrackingHit ?? localHit ?? remoteHit
96 | }
97 |
98 | }
99 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Utils/Helpers/GitAuthor.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GitAuthor.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 GitAuthor: Codable, Hashable, Equatable {
13 | public var name: String
14 | public var email: String
15 |
16 | public init(name: String?, email: String?) {
17 | self.name = name ?? "Unknown"
18 | self.email = email ?? "Unknown"
19 | }
20 |
21 | public func parse(nameAddr: String) -> GitAuthor? {
22 | let value = nameAddr.components(separatedBy: "/^(.*?)\\s+<(.*?)>//")
23 | return value.isEmpty ? nil : GitAuthor(name: value[1],
24 | email: value[2])
25 | }
26 |
27 | public func toString() -> String {
28 | return "\(self.name) \(self.email)"
29 | }
30 |
31 | public static func == (lhs: GitAuthor, rhs: GitAuthor) -> Bool {
32 | lhs.email == rhs.email
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Utils/Helpers/MediaDiff.swift:
--------------------------------------------------------------------------------
1 | //
2 | // File.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/11/25.
6 | //
7 |
8 | import Foundation
9 |
10 | public struct MediaDiff {
11 |
12 | public init() {}
13 |
14 | /// Returns the media type of a file as a string based on its file extension.
15 | ///
16 | /// The function compares the file extension to a set of known image file types
17 | /// and returns the corresponding media type. If the file extension is not recognized
18 | /// as one of the predefined image types, the function defaults to returning "text/plain".
19 | ///
20 | /// - Parameter extension: A string representing the file extension.
21 | /// - Returns: A string representing the media type of the file.
22 | ///
23 | /// # Example:
24 | /// ```
25 | /// let mediaType = getMediaType(extension: ".png") // Returns "image/png"
26 | /// ```
27 | ///
28 | /// - Note: This function currently supports the following image media types:
29 | /// - PNG (.png)
30 | /// - JPEG (.jpg, .jpeg)
31 | /// - GIF (.gif)
32 | /// - ICO (.ico)
33 | /// - WEBP (.webp)
34 | /// - BMP (.bmp)
35 | /// - AVIF (.avif)
36 | func getMediaType(extension: String) -> String {
37 | if `extension` == ".png" {
38 | return "image/png"
39 | }
40 | if `extension` == ".jpg" || `extension` == ".jpeg" {
41 | return "image/jpg"
42 | }
43 | if `extension` == ".gif" {
44 | return "image/gif"
45 | }
46 | if `extension` == ".ico" {
47 | return "image/x-icon"
48 | }
49 | if `extension` == ".webp" {
50 | return "image/webp"
51 | }
52 | if `extension` == ".bmp" {
53 | return "image/bmp"
54 | }
55 | if `extension` == ".avif" {
56 | return "image/avif"
57 | }
58 |
59 | return "text/plain"
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Utils/Helpers/Regex.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Regex.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/10/29.
6 | //
7 |
8 | import Foundation
9 |
10 | /**
11 | This class provides methods to find and extract regex matches and their captured groups
12 | from a given text using NSRegularExpression.
13 | */
14 | class Regex {
15 |
16 | init() {}
17 |
18 | /**
19 | Get captured groups from regex matches within a given text.
20 |
21 | - Parameters:
22 | - text: The input string to search for matches and captures.
23 | - expression: The regular expression to use for matching. It should have the global option.
24 |
25 | - Returns: An array of arrays of strings representing the captured groups from each match. \
26 | The outer array contains one element for each match, and the inner arrays contain the captured strings.
27 | */
28 | func getCaptures(text: String, expression: NSRegularExpression) -> [[String]] {
29 | let matches = getMatches(text: text, expression: expression)
30 | var captures: [[String]] = []
31 |
32 | for match in matches {
33 | let capturedStrings = (1.. [NSTextCheckingResult] {
52 | let range = NSRange(text.startIndex..., in: text)
53 | let matches = expression.matches(in: text, range: range)
54 | return matches
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Utils/Helpers/RemoveRemotePrefix.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RemoveRemotePrefix.swift
3 | //
4 | //
5 | // Created by Nanashi Li on 2023/09/24.
6 | //
7 |
8 | import Foundation
9 |
10 | /// Remove the remote prefix from a branch name.
11 | ///
12 | /// If a branch name includes a remote prefix, \
13 | /// this function extracts the branch name itself by removing the remote prefix. \
14 | /// If no prefix is found, it returns `nil`.
15 | ///
16 | /// - Parameter name: The branch name that may include a remote prefix.
17 | ///
18 | /// - Returns: The branch name without the remote prefix, or `nil` if no remote prefix is present in the input name.
19 | ///
20 | /// - Example:
21 | /// ```swift
22 | /// let branchName = "origin/main" // Replace with the branch name
23 | /// let extractedBranch = removeRemotePrefix(name: branchName)
24 | /// if let branch = extractedBranch {
25 | /// print("Extracted Branch: \(branch)")
26 | /// } else {
27 | /// print("No remote prefix found.")
28 | /// }
29 | /// ```
30 | ///
31 | /// - Note:
32 | /// The remote prefix typically includes the name of the remote repository and a forward slash (`/`). \
33 | /// This function is useful for extracting the local branch name from a branch name that includes the remote prefix.
34 | ///
35 | /// - Warning:
36 | /// Ensure that the input `name` is a valid branch name or includes a remote prefix to avoid unexpected results.
37 | ///
38 | /// - Returns: The extracted branch name or `nil` if no remote prefix is present in the input name.
39 | func removeRemotePrefix(name: String) -> String? {
40 | let regexPattern = #".*?/(.*)"#
41 |
42 | if let regex = try? NSRegularExpression(pattern: regexPattern, options: []) {
43 | if let match = regex.firstMatch(in: name, options: [], range: NSRange(location: 0, length: name.utf16.count)) {
44 | let remoteBranch = (name as NSString).substring(with: match.range(at: 1))
45 | return remoteBranch
46 | }
47 | }
48 |
49 | return nil
50 | }
51 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Utils/LiveShellClient.swift:
--------------------------------------------------------------------------------
1 | //
2 | // shellClient.swift
3 | // AuroraEditor
4 | //
5 | // Created by Wesley de Groot on 22/07/2022.
6 | //
7 |
8 | import Foundation
9 |
10 | public var sharedShellClient: LiveShellClient = .init()
11 |
12 | // Inspired by: https://vimeo.com/291588126
13 | public struct LiveShellClient {
14 | public var shellClient: ShellClient = .live()
15 | }
16 |
--------------------------------------------------------------------------------
/Sources/Version-Control/Version_Control.swift:
--------------------------------------------------------------------------------
1 | public struct VersionControl {
2 | public private(set) var text = "Hello, World!"
3 |
4 | public init() {
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Tests/Version-Control-Test/Services/API/GitHub/Mock Data/GitHubAccountResponse.json:
--------------------------------------------------------------------------------
1 | {
2 | "login": "octocat",
3 | "id": 1,
4 | "node_id": "MDQ6VXNlcjE=",
5 | "avatar_url": "https://github.com/images/error/octocat_happy.gif",
6 | "gravatar_id": "",
7 | "url": "https://api.github.com/users/octocat",
8 | "html_url": "https://github.com/octocat",
9 | "followers_url": "https://api.github.com/users/octocat/followers",
10 | "following_url": "https://api.github.com/users/octocat/following{/other_user}",
11 | "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
12 | "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
13 | "subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
14 | "organizations_url": "https://api.github.com/users/octocat/orgs",
15 | "repos_url": "https://api.github.com/users/octocat/repos",
16 | "events_url": "https://api.github.com/users/octocat/events{/privacy}",
17 | "received_events_url": "https://api.github.com/users/octocat/received_events",
18 | "type": "User",
19 | "site_admin": false,
20 | "name": "monalisa octocat",
21 | "company": "GitHub",
22 | "blog": "https://github.com/blog",
23 | "location": "San Francisco",
24 | "email": "octocat@github.com",
25 | "hireable": false,
26 | "bio": "There once was...",
27 | "twitter_username": "monatheoctocat",
28 | "public_repos": 2,
29 | "public_gists": 1,
30 | "followers": 20,
31 | "following": 0,
32 | "created_at": "2008-01-14T04:33:35Z",
33 | "updated_at": "2008-01-14T04:33:35Z",
34 | "private_gists": 81,
35 | "total_private_repos": 100,
36 | "owned_private_repos": 100,
37 | "disk_usage": 10000,
38 | "collaborators": 8,
39 | "two_factor_authentication": true,
40 | "plan": {
41 | "name": "Medium",
42 | "space": 400,
43 | "private_repos": 20,
44 | "collaborators": 0
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/Tests/Version-Control-Test/Services/API/GitHub/Mock Data/ProtectedBranchesResponse.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "development",
4 | "commit": {
5 | "sha": "c293badc0c44ad4e78ebd2a741731709d8c58d18",
6 | "url": "https://api.github.com/repos/AuroraEditor/AuroraEditor/commits/c293badc0c44ad4e78ebd2a741731709d8c58d18"
7 | },
8 | "protected": true
9 | },
10 | {
11 | "name": "main",
12 | "commit": {
13 | "sha": "7f7b465a3945af5717ef6d9faee57647ac8de79a",
14 | "url": "https://api.github.com/repos/AuroraEditor/AuroraEditor/commits/7f7b465a3945af5717ef6d9faee57647ac8de79a"
15 | },
16 | "protected": true
17 | }
18 | ]
19 |
--------------------------------------------------------------------------------
/Tests/Version-Control-Test/Services/API/GitHub/Mock Data/PushControlResponse.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "development",
3 | "commit": {
4 | "sha": "c293badc0c44ad4e78ebd2a741731709d8c58d18",
5 | "url": "https://api.github.com/repos/AuroraEditor/AuroraEditor/commits/c293badc0c44ad4e78ebd2a741731709d8c58d18"
6 | },
7 | "protected": true,
8 | "pattern": "development",
9 | "required_signatures": false,
10 | "required_status_checks": [],
11 | "required_approving_review_count": 0,
12 | "required_linear_history": false,
13 | "allow_actor": true,
14 | "allow_deletions": false,
15 | "allow_force_pushes": false,
16 | "block_creations": false
17 | }
18 |
--------------------------------------------------------------------------------