Skip to content

Commit

Permalink
Additional Source Control Functionality (#1785)
Browse files Browse the repository at this point in the history
### Description

We are improving upon existing source control functionality by doing the following...

- Added push functionality and sheet. 
- Added pull functionality and sheet. 
- Added manual fetch functionality and fetching sheet.
- Reorganized source control files in project
    - Renamed Features/Git to Features/SourceControl
    - Moved sheet views into it from the Source Control Navigator
    - Moved models out of Client
- Moved the sync view above commit form in source control changes navigator. 
- Refreshing remotes cache after adding a remote. 
- Moved push and pull state variables into the source control manager. 
- Added a confirm branch switch sheet.
- Stashing changes if changes exist when attempting to pull or switch branches. 
- Setting remote for push and pull after adding a remote.
- Added warning alerts if uncommitted changes are not present
- It is now possible to switch to a remote branch and create a new branch from a remote branch. 
- Disabling rename branch for remote branches.


### Testing

Note to reviewers... Please pull down and test this in addition to reviewing the code. Once you have this pulled down...

In a terminal run...
```
mkdir test.git
cd test.git
git init --bare
```
Then add the remote in CodeEdit by 
1. going to the Source Control Navigator
2. If not already click Initiate to start a git repo
3. go to the Repositories tab
4. Right click Remotes and click Add existing remote...
5. Give the remote a name like "test" (the first remote will be "origin") and enter the path to test.git
7. You can now make changes, commit, and push via the Changes tab in the Source Control Navigator
6. You can set up additional projects by repeating steps 1-5 so you can make changes elsewhere and test pull functionality.
7. You can create additional remotes as well to test managing a project with multiple remotes, pushing and pulling from them, etc.

### Checklist

- [x] Add push logic
- [x] Add pull logic
- [x] Add push sheet
- [x] Add pull sheet
- [x] Add centralized sheets view
- [x] Rename Features/Git to Features/SourceControl
- [x] Move sheet views into Features/SourceControl
- [x] Add "Source Control" menu
- [x] Add remote from push and pull sheets
- [x] Set new remote in push or pull sheet after one is added
- [x] Stash changes if attempting to pull or switch branches and changes exist
- [x] Add fetch to the menu and display fetching sheet while fetching
- [x] Add confirm branch switch sheet
- [x] Add warning alerts if uncommitted changes are not present
---
- [x] I read and understood the [contributing guide](https://github.com/CodeEditApp/CodeEdit/blob/main/CONTRIBUTING.md) as well as the [code of conduct](https://github.com/CodeEditApp/CodeEdit/blob/main/CODE_OF_CONDUCT.md)
- [x] The issues this PR addresses are related to each other
- [x] My changes generate no new warnings
- [x] My code builds and runs on my machine
- [x] My changes are all related to the related issue above
- [x] I documented my code

### Screenshots

<img width="598" alt="image" src="https://github.com/CodeEditApp/CodeEdit/assets/806104/e599cb12-0772-4a6e-abca-b5f9af7fa74a">

<img width="598" alt="image" src="https://github.com/CodeEditApp/CodeEdit/assets/806104/eca3d546-d38c-4787-89ed-9930220fd4e4">

<img width="598" alt="image" src="https://github.com/CodeEditApp/CodeEdit/assets/806104/20a5c002-5fff-4920-8211-8333fce56f6b">

<img width="598" alt="image" src="https://github.com/CodeEditApp/CodeEdit/assets/806104/6e80c67b-5356-4cf7-82b7-1ad95a13e05b">

<img width="342" alt="image" src="https://github.com/CodeEditApp/CodeEdit/assets/806104/0e55f5f8-f71b-4e30-bc05-d80a5796360e">

<img width="712" alt="image" src="https://github.com/CodeEditApp/CodeEdit/assets/806104/00c61863-5cb4-409b-b2d6-40098db5c57e">

### Todos discovered

- Reload source editor contents when file is changed externally
- Handle source control errors better and show necessary errors to user
- Handle rebase and merge conflict flow in UI
- ~~Display loading indicator when pushing or pulling~~ Be able to cancel push or pull while in progress
- Display loading indicator when pulling from stash sheet
- Write tests for source control features

### Commits

* Getting tracked branch and changing source control navigator UI if there is not a tracked branch.

* Added push and pull sheet. Renamed Features/Git to Features/SourceControl and moved sheet views into it from the Source Control Navigator.

* Fixed dismiss on submit. Changed sheet width

* Changed navigator tab label

* Removed print statement

* Fixed commit branch and tag design in history tab

* `SourceControlCommands` Impl

* Now that we can access source control features outside the navigator, we need to refresh git data right away rather than on navigator view init.

* Added the ability to add existing remotes from the push or pull sheet and source control menu.

* Added the ability to add existing remotes from the push or pull sheet and source control menu.

* Added ability to fetch changes from the menu

* Split up source control manager file to resolve SwiftLint error

* SwiftLint fixes

* Resolved PR issues

* Moved the sync view above commit form in source control changes navigator. Refereshing remotes cache after adding a remote. Moved push and pull state variables into the source control manager. Stashing changes if changes exist when attempting a pull. Setting remote for push and pull after adding a remote. Renamed scm variables to sourceControlManager for clarity.

* SwiftLint error fixes

* Separated "Apply stash after operation" field into it's own section in stash sheet

* Reset remote and branch after stash

* Moved functions below body in source control views

* Recfactored pull function arguments

* Added more detailed comment for SourceControlManager. Moved Git models.

* Made setUpstream default to false in the push function

* When switching branches, we now have the user confirm. We also check for changes and if there are changes we prompt the user to stash changes before switching branches.

* Added warning alerts if uncommited changes are not present

* Added minimum width to buttons in source control sheets

* Not showing sync view if there are no remotes.

* Formatting branch and remote names in the add sheet.

* Fixed push PR issue

* It is now possible to switch to a remote branch and create a new branch from a remote branch. We are now disabling rename branch for remote branches.

* Removed redundant nil

* Added loading indicators in push and pull sheets

* Fixed SwiftLint errors

* Fixed SwiftLint error

* Added documentation to submit functions in push and pull views

* Renamed a few files that were previously moved and added documentation to new branch and rename branch views

---------

Co-authored-by: Khan Winter <[email protected]>
  • Loading branch information
austincondiff and thecoolwinter authored Aug 8, 2024
1 parent 5eba707 commit a231637
Show file tree
Hide file tree
Showing 117 changed files with 1,516 additions and 513 deletions.
106 changes: 83 additions & 23 deletions CodeEdit.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -186,8 +186,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-collections.git",
"state" : {
"revision" : "ee97538f5b81ae89698fd95938896dec5217b148",
"version" : "1.1.1"
"revision" : "3d2dc41a01f9e49d84f0a3925fb858bed64f702d",
"version" : "1.1.2"
}
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ struct CEContentUnavailableView<Actions: View>: View {
if description != nil {
Text(description ?? "")
.font(.system(size: 10))
.multilineTextAlignment(.center)
}
}
if let actionsView = actions {
Expand Down
2 changes: 1 addition & 1 deletion CodeEdit/Features/CodeEditUI/Views/KeyValueTable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ private struct NewListTableItemView: View {
.padding(.horizontal, 20)
.padding(.bottom, 20)
}
.frame(maxWidth: 480)
.frame(maxWidth: 500)
}
}

Expand Down
17 changes: 0 additions & 17 deletions CodeEdit/Features/Git/Client/GitClient+Pull.swift

This file was deleted.

2 changes: 1 addition & 1 deletion CodeEdit/Features/NavigatorArea/Models/NavigatorTab.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ enum NavigatorTab: AreaTab {
case .project:
return "Project"
case .sourceControl:
return "Version Control"
return "Source Control"
case .search:
return "Search"
case .uiExtension(_, let data):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,15 @@ struct SourceControlNavigatorChangesView: View {
if hasChanges || !hasRemotes || (!hasChanges && (hasUnsyncedCommits || hasCurrentBranch)) {
VStack(spacing: 8) {
Divided {
if hasRemotes && (hasUnsyncedCommits || hasCurrentBranch) {
SourceControlNavigatorSyncView(sourceControlManager: sourceControlManager)
}
if hasChanges {
SourceControlNavigatorChangesCommitView()
}
if !hasRemotes {
SourceControlNavigatorNoRemotesView()
}
if !hasChanges && (hasUnsyncedCommits || hasCurrentBranch) {
SourceControlNavigatorSyncView(sourceControlManager: sourceControlManager)
}
}
}
.padding(.horizontal, 10)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ import SwiftUI
struct SourceControlNavigatorNoRemotesView: View {
@EnvironmentObject var sourceControlManager: SourceControlManager

@State private var addRemoteIsPresented: Bool = false

var body: some View {
VStack(spacing: 0) {
HStack {
Expand All @@ -25,10 +23,7 @@ struct SourceControlNavigatorNoRemotesView: View {
)
Spacer()
Button("Add") {
addRemoteIsPresented = true
}
.sheet(isPresented: $addRemoteIsPresented) {
SourceControlAddRemoteView()
sourceControlManager.addExistingRemoteSheetIsPresented = true
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,41 +12,46 @@ struct SourceControlNavigatorSyncView: View {
@State private var isLoading: Bool = false

var body: some View {
HStack {
Label(title: {
Text(
formatUnsyncedlabel(
ahead: sourceControlManager.numberOfUnsyncedCommits.ahead,
behind: sourceControlManager.numberOfUnsyncedCommits.behind
)
)
}, icon: {
Image(systemName: "arrow.up.arrow.down")
.foregroundStyle(.secondary)
})
Spacer()
if sourceControlManager.numberOfUnsyncedCommits.behind > 0 {
Button {
self.pull()
} label: {
if isLoading {
Text("Pulling...")
} else {
Text("Pull")
}
if let currentBranch = sourceControlManager.currentBranch {
HStack {
if currentBranch.upstream == nil {
Label(title: {
Text("No tracked branch for '\(sourceControlManager.currentBranch?.name ?? "")'")
}, icon: {
Image(symbol: "branch")
.foregroundStyle(.secondary)
})
} else {
Label(title: {
Text(
formatUnsyncedlabel(
ahead: sourceControlManager.numberOfUnsyncedCommits.ahead,
behind: sourceControlManager.numberOfUnsyncedCommits.behind
)
)
}, icon: {
Image(systemName: "arrow.up.arrow.down")
.foregroundStyle(.secondary)
})
}
.disabled(isLoading)
} else if sourceControlManager.numberOfUnsyncedCommits.ahead > 0 {
Button {
self.push()
} label: {
if isLoading {
Text("Pushing...")
} else {
Text("Push")

Spacer()
if sourceControlManager.numberOfUnsyncedCommits.behind > 0 {
Button {
sourceControlManager.pullSheetIsPresented = true
} label: {
Text("Pull...")
}
.disabled(isLoading)
} else if sourceControlManager.numberOfUnsyncedCommits.ahead > 0
|| currentBranch.upstream == nil {
Button {
sourceControlManager.pushSheetIsPresented = true
} label: {
Text("Push...")
}
.disabled(isLoading)
}
.disabled(isLoading)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,19 +88,20 @@ struct CommitListItemView: View {
if !commit.refs.isEmpty {
HStack {
ForEach(commit.refs, id: \.self) { ref in
HStack {
HStack(spacing: 2.5) {
Image.branch
.imageScale(.small)
.foregroundColor(.primary)
.foregroundColor(.secondary)
.help(ref)
Text(ref)
.font(.system(size: 10, design: .monospaced))
}
.font(.system(size: 10))
.frame(height: 13)
.background(
RoundedRectangle(cornerRadius: 3)
.padding(.vertical, -1)
.padding(.horizontal, -2.5)
.padding(.leading, -2.5)
.padding(.trailing, -4)
.foregroundColor(Color(nsColor: .quaternaryLabelColor))
)
.padding(.trailing, 2.5)
Expand All @@ -109,20 +110,21 @@ struct CommitListItemView: View {
}

if !commit.tag.isEmpty {
HStack {
Image.breakpoint
HStack(spacing: 2.5) {
Image(systemName: "tag")
.imageScale(.small)
.foregroundColor(.primary)
.help(commit.tag)
Text(commit.tag)
.font(.system(size: 10, design: .monospaced))
}
.font(.system(size: 10))
.frame(height: 13)
.background(
RoundedRectangle(cornerRadius: 3)
.padding(.vertical, -1)
.padding(.horizontal, -2.5)
.foregroundColor(Color(nsColor: .selectedContentBackgroundColor))
.padding(.leading, -2.5)
.padding(.trailing, -4)
.foregroundColor(Color(nsColor: .purple).opacity(0.2))
)
.padding(.trailing, 2.5)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,10 @@ extension SourceControlNavigatorRepositoryView {
}
}

func handleCheckout(_ branch: GitBranch) {
Task {
do {
try await sourceControlManager.checkoutBranch(branch: branch)
} catch {
await sourceControlManager.showAlertForError(title: "Failed to checkout", error: error)
}
}
}

@ViewBuilder
func contextMenu(for item: RepoOutlineGroupItem, branch: GitBranch) -> some View {
Button("Checkout") {
handleCheckout(branch)
Button("Switch...") {
sourceControlManager.switchToBranch = branch
}
.disabled(item.branch == nil || sourceControlManager.currentBranch == item.branch)
Divider()
Expand All @@ -57,10 +47,10 @@ extension SourceControlNavigatorRepositoryView {
showRenameBranch = true
fromBranch = item.branch
}
.disabled(item.branch == nil)
.disabled(item.branch == nil || item.branch?.isRemote == true)
Divider()
Button("Add Existing Remote...") {
addRemoteIsPresented = true
sourceControlManager.addExistingRemoteSheetIsPresented = true
}
.disabled(item.id != "RemotesGroup")
Divider()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,15 @@ extension SourceControlNavigatorRepositoryView {
label: remote.name,
symbolImage: "vault",
imageColor: .teal,
children: remote.branches.map { branch in
.init(
id: "Remote\(remote.name)-Branch\(branch.name)",
label: branch.name,
symbolImage: "branch",
imageColor: .blue,
branch: branch
)
},
remote: remote
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ struct SourceControlNavigatorRepositoryView: View {
@State var showRenameBranch: Bool = false
@State var fromBranch: GitBranch?
@State var expandedIds = [String: Bool]()
@State var addRemoteIsPresented: Bool = false
@State var applyStashedChangesIsPresented: Bool = false
@State var isPresentingConfirmDeleteBranch: Bool = false
@State var branchToDelete: GitBranch?
Expand Down Expand Up @@ -70,20 +69,15 @@ struct SourceControlNavigatorRepositoryView: View {
}
)
.sheet(isPresented: $showNewBranch) {
SourceControlNavigatorNewBranchView(
sourceControlManager: sourceControlManager,
fromBranch: fromBranch
SourceControlNewBranchView(
fromBranch: $fromBranch
)
}
.sheet(isPresented: $showRenameBranch) {
SourceControlNavigatorRenameBranchView(
sourceControlManager: sourceControlManager,
fromBranch: fromBranch
SourceControlRenameBranchView(
fromBranch: $fromBranch
)
}
.sheet(isPresented: $addRemoteIsPresented) {
SourceControlAddRemoteView()
}
.alert(
sourceControlManager.changedFiles.isEmpty
? "Do you want to apply stashed changes?"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@ struct SourceControlNavigatorToolbarBottom: View {
@EnvironmentObject var sourceControlManager: SourceControlManager

@State private var text = ""
@State private var stashChangesIsPresented = false
@State private var noChangesToStashIsPresented = false
@State private var noDiscardChangesIsPresented = false

var body: some View {
HStack(spacing: 5) {
Expand Down Expand Up @@ -51,20 +48,17 @@ struct SourceControlNavigatorToolbarBottom: View {
private var sourceControlMenu: some View {
Menu {
Button("Discard All Changes...") {
guard let sourceControlManager = workspace.sourceControlManager else { return }
if sourceControlManager.changedFiles.isEmpty {
noDiscardChangesIsPresented = true
return
}
if discardChangesDialog() {
workspace.sourceControlManager?.discardAllChanges()
sourceControlManager.noChangesToDiscardAlertIsPresented = true
} else {
sourceControlManager.discardAllAlertIsPresented = true
}
}
Button("Stash Changes...") {
if sourceControlManager.changedFiles.isEmpty {
noChangesToStashIsPresented = true
sourceControlManager.noChangesToStashAlertIsPresented = true
} else {
stashChangesIsPresented = true
sourceControlManager.stashSheetIsPresented = true
}
}
} label: {}
Expand All @@ -74,31 +68,5 @@ struct SourceControlNavigatorToolbarBottom: View {
.menuStyle(.borderlessButton)
.menuIndicator(.hidden)
.frame(maxWidth: 18, alignment: .center)
.sheet(isPresented: $stashChangesIsPresented) {
SourceControlStashChangesView()
}
.alert("Cannot Stash Changes", isPresented: $noChangesToStashIsPresented) {
Button("OK", role: .cancel) {}
} message: {
Text("There are no uncommitted changes in the local repository for this project.")
}
.alert("Cannot Discard Changes", isPresented: $noDiscardChangesIsPresented) {
Button("OK", role: .cancel) {}
} message: {
Text("There are no uncommitted changes in the local repository for this project.")
}
}

/// Renders a Discard Changes Dialog
func discardChangesDialog() -> Bool {
let alert = NSAlert()

alert.messageText = "Do you want to discard all uncommitted, local changes?"
alert.informativeText = "This operation cannot be undone."
alert.alertStyle = .warning
alert.addButton(withTitle: "Discard")
alert.addButton(withTitle: "Cancel")

return alert.runModal() == .alertFirstButtonReturn
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,6 @@ struct SourceControlNavigatorTabs: View {
.frame(maxWidth: .infinity)
.frame(height: 27)
.padding(.horizontal, 8)
.task {
do {
try await sourceControlManager.refreshRemotes()
try await sourceControlManager.refreshStashEntries()
} catch {
await sourceControlManager.showAlertForError(title: "Error refreshing Git data", error: error)
}
}
Divider()
if selectedSection == 0 {
SourceControlNavigatorChangesView()
Expand Down
Loading

0 comments on commit a231637

Please sign in to comment.