Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
## 1.53.0

### Added

- **Git drives show the current checked-out branch.** The node reports the working
tree's branch on clone/sync; it's persisted on the mount (`MountRecord.currentBranch`)
and shown in `omnyshell drive ls` / `drive status` (a `Branch:` line and `src@branch`
in the list) and the dashboard TUI (a `Branch` row in the mount detail and `@branch`
in the list).

### Fixed

- Git-drive **pull no longer crashes** when the node is on a branch that isn't on the
origin (via omnydrive 1.10.1): the pull no-ops when there's nothing to pull, and
otherwise fetches the branch by name and fast-forwards `FETCH_HEAD`.

## 1.52.2

### Changed
Expand Down
9 changes: 7 additions & 2 deletions bin/omnyshell.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5223,9 +5223,10 @@ class DriveCommand extends Command<void> {
String _mountLine(MountRecord r) {
final st = r.syncState;
final src = r.isGit ? (r.gitUrl ?? 'git') : (r.localPath ?? '?');
final branch = r.isGit ? '@${r.currentBranch ?? r.gitBranch ?? '?'}' : '';
final mode = r.readWrite ? 'rw' : 'ro';
return '${r.id.padRight(22)} ${st.status.wireValue.padRight(10)} '
'$mode $src -> ${r.nodeId}:${r.remotePath}';
'$mode $src$branch -> ${r.nodeId}:${r.remotePath}';
}

class DriveMountCommand extends Command<void> {
Expand Down Expand Up @@ -5418,7 +5419,11 @@ class DriveStatusCommand extends Command<void> {
..writeln(
'Kind: ${r.kind} (${r.readWrite ? 'read-write' : 'read-only'})',
)
..writeln('Source: ${r.isGit ? r.gitUrl : r.localPath}')
..writeln('Source: ${r.isGit ? r.gitUrl : r.localPath}');
if (r.isGit) {
stdout.writeln('Branch: ${r.currentBranch ?? r.gitBranch ?? '?'}');
}
stdout
..writeln('Target: ${r.nodeId}:${r.remotePath}')
..writeln('Status: ${st.status.wireValue}')
..writeln('Baseline: ${st.baselineRef}')
Expand Down
4 changes: 3 additions & 1 deletion lib/src/application/client/dashboard/dashboard_app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2422,9 +2422,10 @@ class DashboardApp {
/// A compact one-line description of a mount (mirrors the CLI's `_mountLine`).
String _mountLine(MountRecord r) {
final src = r.isGit ? (r.gitUrl ?? 'git') : (r.localPath ?? '?');
final branch = r.isGit ? '@${r.currentBranch ?? r.gitBranch ?? '?'}' : '';
final mode = r.readWrite ? 'rw' : 'ro';
return '${_pad(r.id, 22)} ${_pad(r.syncState.status.wireValue, 10)} '
'$mode $src -> ${r.nodeId}:${r.remotePath}';
'$mode $src$branch -> ${r.nodeId}:${r.remotePath}';
}

void _renderDriveDetail(ScreenBuffer s, Rect area) {
Expand All @@ -2436,6 +2437,7 @@ class DashboardApp {
('Mount', m.id),
('Kind', '${m.kind} (${m.readWrite ? 'read-write' : 'read-only'})'),
('Source', m.isGit ? (m.gitUrl ?? '?') : (m.localPath ?? '?')),
if (m.isGit) ('Branch', m.currentBranch ?? m.gitBranch ?? '?'),
('Target', '${m.nodeId}:${m.remotePath}'),
('Status', st.status.wireValue),
('Synced', st.lastSyncedAt?.toIso8601String() ?? 'never'),
Expand Down
15 changes: 9 additions & 6 deletions lib/src/application/client/drive/drive_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -284,12 +284,13 @@ class DriveManager {

record = await _withSession(record, (rpc) async {
_emit(onProgress, ProgressPhase.transferring, 'cloning $url');
final head = await rpc.gitClone(url, branch: branch, depth: depth);
final clone = await rpc.gitClone(url, branch: branch, depth: depth);
_emit(onProgress, ProgressPhase.done, 'cloned');
return record.copyWith(
currentBranch: clone.branch,
syncState: SyncState(
baselineRef: SyncRef.git(head),
currentRef: SyncRef.git(head),
baselineRef: SyncRef.git(clone.head),
currentRef: SyncRef.git(clone.head),
status: SyncStatus.clean,
lastSyncedAt: DateTime.now(),
),
Expand Down Expand Up @@ -699,6 +700,7 @@ class DriveManager {
}
final head = reply['head'] as String;
final updated = record.copyWith(
currentBranch: reply['branch'] as String?,
syncState: SyncState(
baselineRef: SyncRef.git(head),
currentRef: SyncRef.git(head),
Expand Down Expand Up @@ -1146,15 +1148,16 @@ class DriveManager {
ProgressPhase.transferring,
'cloning ${record.gitUrl}',
);
final head = await rpc.gitClone(
final clone = await rpc.gitClone(
record.gitUrl!,
branch: record.gitBranch,
);
_emit(onProgress, ProgressPhase.done, 'cloned');
return record.copyWith(
currentBranch: clone.branch,
syncState: SyncState(
baselineRef: SyncRef.git(head),
currentRef: SyncRef.git(head),
baselineRef: SyncRef.git(clone.head),
currentRef: SyncRef.git(clone.head),
status: SyncStatus.clean,
lastSyncedAt: DateTime.now(),
),
Expand Down
10 changes: 7 additions & 3 deletions lib/src/application/client/drive/drive_rpc_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,9 @@ class DriveRpcClient {
return reply.header['copied'] == true;
}

/// Clones [url] into the served root, returning the resulting head SHA.
Future<String> gitClone(
/// Clones [url] into the served root, returning the resulting head SHA and the
/// checked-out branch.
Future<({String head, String? branch})> gitClone(
String url, {
String? branch,
int? depth,
Expand All @@ -158,7 +159,10 @@ class DriveRpcClient {
if (bare) 'bare': true,
},
);
return reply.header['head'] as String;
return (
head: reply.header['head'] as String,
branch: reply.header['branch'] as String?,
);
}

/// Synchronizes the served git tree in [direction] from [baseline]. Returns
Expand Down
16 changes: 14 additions & 2 deletions lib/src/application/client/drive/mount_store.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,15 @@ class MountRecord {
/// The git repository URL (`git` kind), if any.
final String? gitUrl;

/// The git branch to track (`git` kind), if any.
/// The git branch to track (`git` kind), if any — the branch requested at
/// mount time.
final String? gitBranch;

/// The branch currently checked out on the node (`git` kind), observed from
/// the last clone/sync. May differ from [gitBranch] (e.g. after the node
/// publishes to a feature branch). Null until first observed.
final String? currentBranch;

/// Whether the node mirror is writable (changes can sync back).
final bool readWrite;

Expand Down Expand Up @@ -79,6 +85,7 @@ class MountRecord {
this.localPath,
this.gitUrl,
this.gitBranch,
this.currentBranch,
this.ephemeral = false,
this.baselineManifest,
PathFilter? filter,
Expand All @@ -91,10 +98,12 @@ class MountRecord {
AccessMode get accessMode =>
readWrite ? AccessMode.readWrite : AccessMode.readOnly;

/// Returns a copy with a new [syncState] and/or [baselineManifest].
/// Returns a copy with a new [syncState], [baselineManifest] and/or
/// [currentBranch].
MountRecord copyWith({
SyncState? syncState,
FileManifest? baselineManifest,
String? currentBranch,
}) => MountRecord(
id: id,
nodeId: nodeId,
Expand All @@ -109,6 +118,7 @@ class MountRecord {
localPath: localPath,
gitUrl: gitUrl,
gitBranch: gitBranch,
currentBranch: currentBranch ?? this.currentBranch,
ephemeral: ephemeral,
filter: filter,
);
Expand All @@ -122,6 +132,7 @@ class MountRecord {
if (localPath != null) 'localPath': localPath,
if (gitUrl != null) 'gitUrl': gitUrl,
if (gitBranch != null) 'gitBranch': gitBranch,
if (currentBranch != null) 'currentBranch': currentBranch,
'readWrite': readWrite,
if (ephemeral) 'ephemeral': true,
'driveId': driveId,
Expand All @@ -142,6 +153,7 @@ class MountRecord {
localPath: json['localPath'] as String?,
gitUrl: json['gitUrl'] as String?,
gitBranch: json['gitBranch'] as String?,
currentBranch: json['currentBranch'] as String?,
readWrite: json['readWrite'] as bool? ?? false,
ephemeral: json['ephemeral'] as bool? ?? false,
driveId: json['driveId'] as String,
Expand Down
7 changes: 5 additions & 2 deletions lib/src/application/node/node_drive_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,8 @@ class NodeDriveService {
_reply(await _gitSync(id, msg.header));
case DriveOp.gitHead:
final head = await git.revParse(_root);
_reply(DriveMessage.ok(id, fields: {'head': head}));
final branch = await git.currentBranch(_root);
_reply(DriveMessage.ok(id, fields: {'head': head, 'branch': branch}));
default:
_reply(DriveMessage.err(id, 'unknown op: ${msg.op}'));
}
Expand Down Expand Up @@ -255,7 +256,8 @@ class NodeDriveService {
accessMode: _readWrite ? AccessMode.readWrite : AccessMode.readOnly,
);
final head = await git.revParse(_root);
return DriveMessage.ok(id, fields: {'head': head});
final branch = await git.currentBranch(_root);
return DriveMessage.ok(id, fields: {'head': head, 'branch': branch});
}

Future<DriveMessage> _gitSync(int id, Map<String, dynamic> h) async {
Expand Down Expand Up @@ -299,6 +301,7 @@ class NodeDriveService {
id,
fields: {
'head': result.newRef.value,
'branch': await git.currentBranch(_root),
'applied': result.appliedChanges,
if (result.publishedBranch != null)
'publishedBranch': result.publishedBranch,
Expand Down
2 changes: 1 addition & 1 deletion lib/src/version.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
/// This is the single source of truth for "what build is this": it is rendered
/// in the CLI banner and is the default a node reports as its
/// [NodeConfig.agentVersion] / [PlatformInfo.agentVersion].
const String omnyShellVersion = '1.52.2';
const String omnyShellVersion = '1.53.0';
4 changes: 2 additions & 2 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ description: >-
connect to a Hub by node identity (not host:port); the Hub authenticates,
authorizes and brokers encrypted sessions to Nodes over WebSocket-on-TLS.
Ships Hub, Node, Client and CLI implementations behind first-class Dart APIs.
version: 1.52.2
version: 1.53.0
repository: https://github.com/OmnyGrid/omnyshell

environment:
Expand All @@ -22,7 +22,7 @@ dependencies:
ffi: ^2.1.0
http: ^1.2.0
meta: ^1.18.3
omnydrive: ^1.10.0
omnydrive: ^1.10.1
path: ^1.9.0
pointycastle: ^4.0.0
#portable_pty: ^0.0.5 # waiting crash fix.
Expand Down
7 changes: 7 additions & 0 deletions test/integration/drive_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -869,6 +869,13 @@ void main() {
expect(rec.kind, 'git');
expect(rec.syncState.baselineRef.value, isNotEmpty);
expect(File('${tmp.path}/clone/main.dart').existsSync(), isTrue);
// The node reports the checked-out branch, captured on the record.
final head = await Process.run('git', [
'rev-parse',
'--abbrev-ref',
'HEAD',
], workingDirectory: origin.path);
expect(rec.currentBranch, (head.stdout as String).trim());
});

test('unmount forgets the mount', () async {
Expand Down
Loading