Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CodeEdit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
5CAD1B972806B57D0059A74E /* Breadcrumbs in Frameworks */ = {isa = PBXBuildFile; productRef = 5CAD1B962806B57D0059A74E /* Breadcrumbs */; };
5CF38A5E27E48E6C0096A0F7 /* CodeFile in Frameworks */ = {isa = PBXBuildFile; productRef = 5CF38A5D27E48E6C0096A0F7 /* CodeFile */; };
5CFA753B27E896B60002F01B /* GitClient in Frameworks */ = {isa = PBXBuildFile; productRef = 5CFA753A27E896B60002F01B /* GitClient */; };
B34B213227ECDC3A006033A9 /* GitClone in Frameworks */ = {isa = PBXBuildFile; productRef = B34B213127ECDC3A006033A9 /* GitClone */; };
64B64EDE27F7B79400C400F1 /* About in Frameworks */ = {isa = PBXBuildFile; productRef = 64B64EDD27F7B79400C400F1 /* About */; };
B658FB3427DA9E1000EA4DBD /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B658FB3327DA9E1000EA4DBD /* Assets.xcassets */; };
B658FB3727DA9E1000EA4DBD /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B658FB3627DA9E1000EA4DBD /* Preview Assets.xcassets */; };
Expand Down Expand Up @@ -158,6 +159,7 @@
D70F5E2C27E4E8CF004EE4B9 /* WelcomeModule in Frameworks */,
2859B93F27EB50050069BE88 /* FontPicker in Frameworks */,
D7F72DEB27EA3574000C3064 /* Search in Frameworks */,
B34B213227ECDC3A006033A9 /* GitClone in Frameworks */,
5CFA753B27E896B60002F01B /* GitClient in Frameworks */,
28CE5EA027E6493D0065D29C /* StatusBar in Frameworks */,
20D2A95527F72E6800E7ECF6 /* Accounts in Frameworks */,
Expand Down Expand Up @@ -382,6 +384,7 @@
0485EB2427E7B9C800138301 /* Overlays */,
5CFA753A27E896B60002F01B /* GitClient */,
D7F72DEA27EA3574000C3064 /* Search */,
B34B213127ECDC3A006033A9 /* GitClone */,
2859B93E27EB50050069BE88 /* FontPicker */,
2803257027F3CF1F009C7DC2 /* AppPreferences */,
20D2A95427F72E6800E7ECF6 /* Accounts */,
Expand Down Expand Up @@ -1049,6 +1052,10 @@
isa = XCSwiftPackageProductDependency;
productName = GitClient;
};
B34B213127ECDC3A006033A9 /* GitClone */ = {
isa = XCSwiftPackageProductDependency;
productName = GitClone;
};
64B64EDD27F7B79400C400F1 /* About */ = {
isa = XCSwiftPackageProductDependency;
productName = About;
Expand Down
3 changes: 3 additions & 0 deletions CodeEditModules/Modules/GitClient/src/Interface.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public struct GitClient {
public var getBranches: () throws -> [String]
public var checkoutBranch: (String) throws -> Void
public var pull: () throws -> Void
public var cloneRepository: (String) throws -> Void
/// Get commit history
/// - Parameters:
/// - entries: number of commits we want to fetch. Will use max if nil
Expand All @@ -24,12 +25,14 @@ public struct GitClient {
getBranches: @escaping () throws -> [String],
checkoutBranch: @escaping (String) throws -> Void,
pull: @escaping () throws -> Void,
cloneRepository: @escaping (String) throws -> Void,
getCommitHistory: @escaping (_ entries: Int?, _ fileLocalPath: String?) throws -> [Commit]
) {
self.getCurrentBranchName = getCurrentBranchName
self.getBranches = getBranches
self.checkoutBranch = checkoutBranch
self.pull = pull
self.cloneRepository = cloneRepository
self.getCommitHistory = getCommitHistory
}

Expand Down
7 changes: 7 additions & 0 deletions CodeEditModules/Modules/GitClient/src/Live.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ public extension GitClient {
throw GitClientError.outputError(output)
}
}
func cloneRepository(url: String) throws {
let output = try shellClient.run("cd \(directoryURL.relativePath);git clone \(url) .")
if output.contains("fatal") {
throw GitClientError.outputError(output)
}
}

func getCommitHistory(entries: Int?, fileLocalPath: String?) throws -> [Commit] {
var entriesString = ""
Expand Down Expand Up @@ -79,6 +85,7 @@ public extension GitClient {
throw GitClientError.notGitRepository
}
},
cloneRepository: cloneRepository(url:),
getCommitHistory: getCommitHistory(entries:fileLocalPath:)
)
}
Expand Down
171 changes: 171 additions & 0 deletions CodeEditModules/Modules/GitClone/src/GitCloneView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
//
// GitCloneView.swift
// CodeEdit
//
// Created by Aleksi Puttonen on 23.3.2022.
//

import SwiftUI
import GitClient
import Foundation
import ShellClient

public struct GitCloneView: View {
private let shellClient: ShellClient
@Binding private var isPresented: Bool
@State private var repoUrlStr = ""
@State private var repoPath = "~/"
public init(shellClient: ShellClient, isPresented: Binding<Bool>) {
self.shellClient = shellClient
self._isPresented = isPresented
}
public var body: some View {
VStack(spacing: 8) {
HStack {
Image(nsImage: NSApp.applicationIconImage)
.resizable()
.frame(width: 64, height: 64)
.padding(.bottom, 50)
VStack(alignment: .leading) {
Text("Clone an existing repository")
.bold()
.padding(.bottom, 2)
Text("Enter a git repository URL:")
.font(.system(size: 11))
.foregroundColor(.secondary)
.alignmentGuide(.trailing) { context in
context[.trailing]
}
TextField("Git Repository URL", text: $repoUrlStr)
.lineLimit(1)
.padding(.bottom, 15)
.frame(width: 300)
HStack {
Button("Cancel") {
isPresented = false
}
Button("Clone") {
cloneRepository()
}
.keyboardShortcut(.defaultAction)
.disabled(!isValid(url: repoUrlStr))
}
.offset(x: 185)
.alignmentGuide(.leading) { context in
context[.leading]
}
}
}
.padding(.top, 20)
.padding(.horizontal, 20)
.padding(.bottom, 16)
.onAppear {
self.checkClipboard(textFieldText: &repoUrlStr)
}
}
}
}

extension GitCloneView {
func getPath(modifiable: inout String, saveName: String) -> String? {
let dialog = NSSavePanel()
dialog.showsResizeIndicator = true
dialog.showsHiddenFiles = false
dialog.showsTagField = false
dialog.prompt = "Clone"
dialog.nameFieldStringValue = saveName
dialog.nameFieldLabel = "Clone as"
dialog.title = "Clone"

if dialog.runModal() == NSApplication.ModalResponse.OK {
let result = dialog.url

if result != nil {
let path: String = result!.path
// path contains the directory path e.g
// /Users/ourcodeworld/Desktop/folder
modifiable = path
return path
}
} else {
// User clicked on "Cancel"
return nil
}
return nil
}
func showAlert(alertMsg: String, infoText: String) {
let alert = NSAlert()
alert.messageText = alertMsg
alert.informativeText = infoText
alert.addButton(withTitle: "OK")
alert.alertStyle = .warning
alert.runModal()
}
func isValid(url: String) -> Bool {
// Doing the same kind of check that Xcode does when cloning
let url = url.lowercased()
if url.starts(with: "http://") && url.count > 7 {
return true
} else if url.starts(with: "https://") && url.count > 8 {
return true
} else if url.starts(with: "git@") && url.count > 4 {
return true
}
return false
}
func checkClipboard(textFieldText: inout String) {
if let url = NSPasteboard.general.pasteboardItems?.first?.string(forType: .string) {
if isValid(url: url) {
textFieldText = url
}
}
}
private func cloneRepository() {
do {
if repoUrlStr == "" {
showAlert(alertMsg: "Url cannot be empty",
infoText: "You must specify a repository to clone")
return
}
// Parsing repo name
let repoURL = URL(string: repoUrlStr)
if var repoName = repoURL?.lastPathComponent {
// Strip .git from name if it has it.
// Cloning repository without .git also works
if repoName.contains(".git") {
repoName.removeLast(4)
}
guard getPath(modifiable: &repoPath, saveName: repoName) != nil else {
return
}
} else {
return
}
guard let dirUrl = URL(string: repoPath) else {
return
}
var isDir: ObjCBool = true
if FileManager.default.fileExists(atPath: repoPath, isDirectory: &isDir) {
showAlert(alertMsg: "Error", infoText: "Directory already exists")
return
}
try FileManager.default.createDirectory(atPath: repoPath,
withIntermediateDirectories: true,
attributes: nil)
try GitClient.default(directoryURL: dirUrl,
shellClient: shellClient).cloneRepository(repoUrlStr)
// TODO: Maybe add possibility to checkout to certain branch straight after cloning
isPresented = false
} catch {
guard let error = error as? GitClient.GitClientError else {
return showAlert(alertMsg: "Error", infoText: error.localizedDescription)
}
switch error {
case let .outputError(message):
showAlert(alertMsg: "Error", infoText: message)
case .notGitRepository:
showAlert(alertMsg: "Error", infoText: "Not git repository")
}
}
}
}
11 changes: 8 additions & 3 deletions CodeEditModules/Modules/WelcomeModule/src/WelcomeView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ import SwiftUI
import AppKit
import Foundation
import AppPreferences
import GitClone

public struct WelcomeView: View {
@Environment(\.colorScheme) var colorScheme
@State var showGitClone = false
@State var isHovering: Bool = false
@State var isHoveringClose: Bool = false
@StateObject private var prefs: AppPreferencesModel = .shared
Expand Down Expand Up @@ -157,9 +159,9 @@ public struct WelcomeView: View {
comment: ""
)
)
.onTapGesture {
// TODO: clone a Git repository
}
.onTapGesture {
showGitClone = true
}
}
}
Spacer()
Expand Down Expand Up @@ -198,6 +200,9 @@ public struct WelcomeView: View {
.transition(AnyTransition.opacity.animation(.easeInOut(duration: 0.25)))
}
}
.sheet(isPresented: $showGitClone) {
GitCloneView(shellClient: .live, isPresented: $showGitClone)
}
}

private var dismissButton: some View {
Expand Down
12 changes: 12 additions & 0 deletions CodeEditModules/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ let package = Package(
name: "Search",
targets: ["Search"]
),
.library(
name: "GitClone",
targets: ["GitClone"]
),
.library(
name: "FontPicker",
targets: ["FontPicker"]
Expand Down Expand Up @@ -209,6 +213,14 @@ let package = Package(
],
path: "Modules/Search/src"
),
.target(
name: "GitClone",
dependencies: [
"GitClient",
"ShellClient"
],
path: "Modules/GitClone/src"
),
.target(
name: "FontPicker",
path: "Modules/FontPicker/src"
Expand Down