Skip to content

Commit 20359fd

Browse files
Merge pull request #11 from DevThibautMonin/features/fetch-data
## Version (1.0.13) (02/01/2026) ### Added - [Repository] Fetch remote data. - [Branches] Checkout remote branches locally. - [UX/UI] Resizeable left sidebar with persistent values. Double click to reset to the default value. - [Commit history] Show unpushed commits with a trailing icon. ### Fixes - [Working directory / Commit history] Unselect file when committing, switching feature in sidebar. - [Commit history] Refresh state correctly.
2 parents e94ff7a + c806fe2 commit 20359fd

41 files changed

Lines changed: 620 additions & 49 deletions

Some content is hidden

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

CHANGELOG.md

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
11
# Changelog
22

3+
## Version (1.0.13) (02/01/2026)
4+
5+
### Added
6+
7+
- [Repository] Fetch remote data.
8+
- [Branches] Checkout remote branches locally.
9+
- [UX/UI] Resizeable left sidebar with persistent values. Double click to reset to the default value.
10+
- [Commit history] Show unpushed commits with a trailing icon.
11+
12+
### Fixes
13+
14+
- [Working directory / Commit history] Unselect file when committing, switching feature in sidebar.
15+
- [Commit history] Refresh state correctly.
16+
317
## Version (1.0.12) (02/01/2026)
418

519
### Added
@@ -28,7 +42,7 @@
2842

2943
## Version (1.0.8) (30/12/2025)
3044

31-
### Fix
45+
### Fixes
3246

3347
- [App] Fix auto updater.
3448

@@ -57,7 +71,7 @@
5771

5872
- [Working directory] Discard all changes.
5973

60-
### Fix
74+
### Fixes
6175

6276
- [Branches] Fix branches remote error while creating local branch.
6377

@@ -75,7 +89,7 @@
7589

7690
## Version (1.0.1) (28/12/2025)
7791

78-
### Fix
92+
### Fixes
7993

8094
- [Branches] Fix git branch checkout.
8195

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,14 @@ Make Git easier to understand, safer to use, and more enjoyable, without hiding
1212

1313
---
1414

15+
# 🖥️ Supported platforms
16+
17+
| Platform | Status |
18+
|----------|--------|
19+
| **macOS** | ✅ Supported |
20+
| **Windows** | ⏳ Soon |
21+
| **Linux** | ⏳ Soon |
22+
1523
# ⚙️ Installation
1624

1725
## macOS (Homebrew) - *Recommended*
@@ -51,6 +59,8 @@ brew upgrade --cask opengit
5159
- Create and checkout a new branch.
5260
- Delete branches.
5361
- Rename local branches.
62+
- Fetch remote branches.
63+
- Checkout remote branches locally.
5464

5565
### Working Directory
5666
- Display modified, added, deleted, and untracked files.
@@ -79,6 +89,7 @@ brew upgrade --cask opengit
7989
- Display author, date, and message.
8090
- Clean and readable chronological view.
8191
- Commit diff split view from history.
92+
- Show unpushed commits.
8293

8394
### SSH & Authentication
8495
- Automatic detection of SSH issues.
@@ -88,6 +99,9 @@ brew upgrade --cask opengit
8899
- manage SSH permission errors
89100
- Help converting HTTPS remotes to SSH.
90101

102+
### UX/UI
103+
- Resizeable areas.
104+
91105
---
92106

93107
# 📦 Roadmap

lib/features/branches/presentation/bloc/branches_bloc.dart

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,21 @@ class BranchesBloc extends Bloc<BranchesEvent, BranchesState> {
2222
emit(state.copyWith(status: event.status));
2323
});
2424

25+
on<CheckoutRemoteBranch>((event, emit) async {
26+
try {
27+
await gitService.checkoutRemoteBranch(event.branch.name);
28+
29+
add(GetRepositoryBranches());
30+
} catch (e) {
31+
emit(
32+
state.copyWith(
33+
status: BranchesBlocStatus.error,
34+
errorMessage: e.toString(),
35+
),
36+
);
37+
}
38+
});
39+
2540
on<AskForRenamingBranch>((event, emit) async {
2641
final hasUpstream = await gitService.branchHasUpstream(event.branch.name);
2742

lib/features/branches/presentation/bloc/branches_event.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,11 @@ class AskForRenamingBranch extends BranchesEvent {
6363
required this.branch,
6464
});
6565
}
66+
67+
class CheckoutRemoteBranch extends BranchesEvent {
68+
final BranchEntity branch;
69+
70+
CheckoutRemoteBranch({
71+
required this.branch,
72+
});
73+
}

lib/features/branches/presentation/bloc/branches_state.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ enum BranchesBlocStatus {
1111
askForDeletingBranch,
1212
askForRenamingBranch,
1313
branchRenamed,
14+
fetchingBranches,
1415
}
1516

1617
@MappableClass()
@@ -28,4 +29,10 @@ class BranchesState with BranchesStateMappable {
2829
this.selectedBranch,
2930
this.selectedBranchHasUpstream = false,
3031
});
32+
33+
List<BranchEntity> get currentBranch => branches.where((b) => b.isCurrent).toList();
34+
35+
List<BranchEntity> get localBranches => branches.where((b) => !b.isRemote && !b.isCurrent).toList();
36+
37+
List<BranchEntity> get remoteOnlyBranches => branches.where((b) => b.isRemote && !b.existsLocally).toList();
3138
}

lib/features/branches/presentation/ui/branch_item.dart

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -47,19 +47,34 @@ class _BranchItemState extends State<BranchItem> {
4747
},
4848
child: const Text('Rename branch'),
4949
),
50-
PopupMenuItem(
51-
onTap: () {
52-
context.read<BranchesBloc>()
53-
..add(UpdateBranchesStatus(status: BranchesBlocStatus.askForDeletingBranch))
54-
..add(UpdateSelectedBranch(branch: widget.branch));
55-
},
56-
child: const Text('Delete branch'),
57-
),
50+
if (!widget.branch.isCurrent)
51+
PopupMenuItem(
52+
onTap: () {
53+
context.read<BranchesBloc>()
54+
..add(UpdateSelectedBranch(branch: widget.branch))
55+
..add(UpdateBranchesStatus(status: BranchesBlocStatus.askForDeletingBranch));
56+
},
57+
child: const Text('Delete branch'),
58+
),
5859
],
5960
);
6061
},
6162
onDoubleTap: () {
62-
context.read<BranchesBloc>().add(SwitchToBranch(branch: widget.branch));
63+
final bloc = context.read<BranchesBloc>();
64+
65+
if (widget.branch.isRemote && !widget.branch.existsLocally) {
66+
bloc.add(
67+
CheckoutRemoteBranch(
68+
branch: widget.branch,
69+
),
70+
);
71+
} else {
72+
bloc.add(
73+
SwitchToBranch(
74+
branch: widget.branch,
75+
),
76+
);
77+
}
6378
},
6479
child: AnimatedContainer(
6580
duration: const Duration(milliseconds: 120),
@@ -76,17 +91,19 @@ class _BranchItemState extends State<BranchItem> {
7691
child: Row(
7792
children: [
7893
Icon(
79-
Icons.call_split,
80-
size: 16,
94+
widget.branch.isRemote ? Icons.cloud_outlined : Icons.call_split,
95+
size: 18,
8196
color: isCurrent ? theme.colorScheme.primary : theme.iconTheme.color?.withValues(alpha: 0.6),
8297
),
8398
Gaps.w8,
8499
Expanded(
85100
child: Text(
86101
widget.branch.name,
102+
87103
overflow: TextOverflow.ellipsis,
88104
style: theme.textTheme.bodyMedium?.copyWith(
89-
fontWeight: isCurrent ? FontWeight.w600 : FontWeight.normal,
105+
fontSize: 14,
106+
fontWeight: isCurrent ? FontWeight.w800 : FontWeight.normal,
90107
),
91108
),
92109
),

lib/features/branches/presentation/ui/branches_sidebar.dart

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import 'package:flutter/material.dart';
22
import 'package:flutter_bloc/flutter_bloc.dart';
33
import 'package:open_git/features/branches/presentation/bloc/branches_bloc.dart';
4-
import 'package:open_git/features/branches/presentation/ui/branch_item.dart';
4+
import 'package:open_git/features/branches/presentation/ui/current_branch_section.dart';
5+
import 'package:open_git/features/branches/presentation/ui/local_branches_section.dart';
6+
import 'package:open_git/features/branches/presentation/ui/remote_branches_Section.dart';
57
import 'package:open_git/shared/presentation/widgets/gaps.dart';
68

79
class BranchesSidebar extends StatelessWidget {
@@ -49,13 +51,18 @@ class BranchesSidebar extends StatelessWidget {
4951
Expanded(
5052
child: BlocBuilder<BranchesBloc, BranchesState>(
5153
builder: (context, state) {
52-
return ListView.builder(
53-
itemCount: state.branches.length,
54-
itemBuilder: (context, index) {
55-
final branch = state.branches[index];
54+
return ListView(
55+
children: [
56+
if (state.currentBranch.isNotEmpty) CurrentBranchSection(branch: state.currentBranch.first),
5657

57-
return BranchItem(branch: branch);
58-
},
58+
LocalBranchesSection(
59+
branches: state.localBranches,
60+
),
61+
62+
RemoteBranchesSection(
63+
branches: state.remoteOnlyBranches,
64+
),
65+
],
5966
);
6067
},
6168
),
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:open_git/features/branches/presentation/ui/branch_item.dart';
3+
import 'package:open_git/shared/domain/entities/branch_entity.dart';
4+
5+
class CurrentBranchSection extends StatelessWidget {
6+
final BranchEntity branch;
7+
8+
const CurrentBranchSection({
9+
super.key,
10+
required this.branch,
11+
});
12+
13+
@override
14+
Widget build(BuildContext context) {
15+
final theme = Theme.of(context);
16+
17+
return Column(
18+
crossAxisAlignment: CrossAxisAlignment.start,
19+
children: [
20+
Padding(
21+
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
22+
child: Text(
23+
"Current",
24+
style: theme.textTheme.labelMedium,
25+
),
26+
),
27+
BranchItem(branch: branch),
28+
],
29+
);
30+
}
31+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:open_git/features/branches/presentation/ui/branch_item.dart';
3+
import 'package:open_git/shared/domain/entities/branch_entity.dart';
4+
import 'package:open_git/shared/presentation/widgets/gaps.dart';
5+
6+
class LocalBranchesSection extends StatelessWidget {
7+
final List<BranchEntity> branches;
8+
9+
const LocalBranchesSection({
10+
super.key,
11+
required this.branches,
12+
});
13+
14+
@override
15+
Widget build(BuildContext context) {
16+
final theme = Theme.of(context);
17+
18+
if (branches.isEmpty) return const SizedBox.shrink();
19+
20+
return Column(
21+
crossAxisAlignment: CrossAxisAlignment.start,
22+
children: [
23+
Gaps.h8,
24+
const Divider(height: 1),
25+
Padding(
26+
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
27+
child: Text(
28+
"Local branches",
29+
style: theme.textTheme.labelMedium,
30+
),
31+
),
32+
...branches.map((b) {
33+
return BranchItem(branch: b);
34+
}),
35+
],
36+
);
37+
}
38+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:open_git/features/branches/presentation/ui/branch_item.dart';
3+
import 'package:open_git/shared/domain/entities/branch_entity.dart';
4+
import 'package:open_git/shared/presentation/widgets/gaps.dart';
5+
6+
class RemoteBranchesSection extends StatelessWidget {
7+
final List<BranchEntity> branches;
8+
9+
const RemoteBranchesSection({
10+
super.key,
11+
required this.branches,
12+
});
13+
14+
@override
15+
Widget build(BuildContext context) {
16+
final theme = Theme.of(context);
17+
18+
if (branches.isEmpty) return const SizedBox.shrink();
19+
20+
return Column(
21+
crossAxisAlignment: CrossAxisAlignment.start,
22+
children: [
23+
Gaps.h8,
24+
const Divider(height: 1),
25+
Padding(
26+
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
27+
child: Text(
28+
"Remote branches",
29+
style: theme.textTheme.labelMedium,
30+
),
31+
),
32+
...branches.map((b) {
33+
return BranchItem(branch: b);
34+
}),
35+
],
36+
);
37+
}
38+
}

0 commit comments

Comments
 (0)