mirror of
https://github.com/GitJournal/GitJournal.git
synced 2025-06-26 00:29:20 +08:00
clone: Split into multiple files and make it more pluggable
This way I can more easily choose if I want to use libgit2 or the git executable or whatever. It's being done so that one can wrtie automated tests for this cloneRemote function. There are still some users for whom this doesn't work, and after so so many iterations, it's time to write tests. This is just way too complex for me without tests.
This commit is contained in:
@ -1,13 +1,11 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io' show Platform, Directory, File;
|
import 'dart:io' show Directory, File;
|
||||||
|
|
||||||
import 'package:dart_git/dart_git.dart';
|
import 'package:dart_git/dart_git.dart';
|
||||||
import 'package:dart_git/exceptions.dart';
|
import 'package:dart_git/exceptions.dart';
|
||||||
import 'package:function_types/function_types.dart';
|
import 'package:function_types/function_types.dart';
|
||||||
import 'package:git_bindings/git_bindings.dart' as git_bindings;
|
|
||||||
import 'package:path/path.dart' as p;
|
import 'package:path/path.dart' as p;
|
||||||
|
|
||||||
import 'package:gitjournal/utils/git_desktop.dart';
|
|
||||||
import 'package:gitjournal/utils/logger.dart';
|
import 'package:gitjournal/utils/logger.dart';
|
||||||
|
|
||||||
class GitTransferProgress {
|
class GitTransferProgress {
|
||||||
@ -25,8 +23,6 @@ class GitTransferProgress {
|
|||||||
}
|
}
|
||||||
var str = await File(statusFile).readAsString();
|
var str = await File(statusFile).readAsString();
|
||||||
var parts = str.split(' ');
|
var parts = str.split(' ');
|
||||||
print('Str #$str#');
|
|
||||||
print('Parts $parts');
|
|
||||||
|
|
||||||
var tp = GitTransferProgress();
|
var tp = GitTransferProgress();
|
||||||
tp.totalObjects = int.parse(parts[0]);
|
tp.totalObjects = int.parse(parts[0]);
|
||||||
@ -40,7 +36,32 @@ class GitTransferProgress {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Result<void>> cloneRemote({
|
typedef GitFetchFunction = Future<Result<void>> Function(
|
||||||
|
String repoPath,
|
||||||
|
String remoteName,
|
||||||
|
String sshPublicKey,
|
||||||
|
String sshPrivateKey,
|
||||||
|
String sshPassword,
|
||||||
|
String statusFile,
|
||||||
|
);
|
||||||
|
|
||||||
|
typedef GitDefaultBranchFunction = Future<Result<String>> Function(
|
||||||
|
String repoPath,
|
||||||
|
String remoteName,
|
||||||
|
String sshPublicKey,
|
||||||
|
String sshPrivateKey,
|
||||||
|
String sshPassword,
|
||||||
|
);
|
||||||
|
|
||||||
|
typedef GitMergeFn = Future<Result<void>> Function(
|
||||||
|
String repoPath,
|
||||||
|
String remoteName,
|
||||||
|
String remoteBranchName,
|
||||||
|
String authorName,
|
||||||
|
String authorEmail,
|
||||||
|
);
|
||||||
|
|
||||||
|
Future<Result<void>> cloneRemotePluggable({
|
||||||
required String repoPath,
|
required String repoPath,
|
||||||
required String cloneUrl,
|
required String cloneUrl,
|
||||||
required String remoteName,
|
required String remoteName,
|
||||||
@ -50,61 +71,38 @@ Future<Result<void>> cloneRemote({
|
|||||||
required String authorName,
|
required String authorName,
|
||||||
required String authorEmail,
|
required String authorEmail,
|
||||||
required Func1<GitTransferProgress, void> progressUpdate,
|
required Func1<GitTransferProgress, void> progressUpdate,
|
||||||
|
required GitFetchFunction gitFetchFn,
|
||||||
|
required GitDefaultBranchFunction defaultBranchFn,
|
||||||
|
required GitMergeFn gitMergeFn,
|
||||||
}) async {
|
}) async {
|
||||||
|
// FIXME: Do not throw exceptions
|
||||||
var repo = await GitRepository.load(repoPath).getOrThrow();
|
var repo = await GitRepository.load(repoPath).getOrThrow();
|
||||||
var remote = await repo.addOrUpdateRemote(remoteName, cloneUrl).getOrThrow();
|
var remote = await repo.addOrUpdateRemote(remoteName, cloneUrl).getOrThrow();
|
||||||
|
|
||||||
var remoteBranchName = "master";
|
|
||||||
var _gitRepo = git_bindings.GitRepo(folderPath: repoPath);
|
|
||||||
|
|
||||||
if (Platform.isAndroid || Platform.isIOS) {
|
|
||||||
var statusFile = p.join(Directory.systemTemp.path, 'gj');
|
var statusFile = p.join(Directory.systemTemp.path, 'gj');
|
||||||
var duration = const Duration(milliseconds: 10);
|
var duration = const Duration(milliseconds: 50);
|
||||||
var timer = Timer.periodic(duration, (_) async {
|
var timer = Timer.periodic(duration, (_) async {
|
||||||
var progress = await GitTransferProgress.load(statusFile);
|
var progress = await GitTransferProgress.load(statusFile);
|
||||||
if (progress != null) {
|
if (progress != null) {
|
||||||
progressUpdate(progress);
|
progressUpdate(progress);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
await _gitRepo.fetch(
|
|
||||||
remote: remoteName,
|
var fetchR = await gitFetchFn(repoPath, remoteName, sshPublicKey,
|
||||||
publicKey: sshPublicKey,
|
sshPrivateKey, sshPassword, statusFile);
|
||||||
privateKey: sshPrivateKey,
|
|
||||||
password: sshPassword,
|
|
||||||
statusFile: statusFile,
|
|
||||||
);
|
|
||||||
timer.cancel();
|
timer.cancel();
|
||||||
|
if (fetchR.isFailure) {
|
||||||
remoteBranchName = await _remoteDefaultBranch(
|
// FIXME: Give a better error?
|
||||||
repo: repo,
|
return fail(fetchR);
|
||||||
libGit2Repo: _gitRepo,
|
|
||||||
remoteName: remoteName,
|
|
||||||
sshPublicKey: sshPublicKey,
|
|
||||||
sshPrivateKey: sshPrivateKey,
|
|
||||||
sshPassword: sshPassword,
|
|
||||||
);
|
|
||||||
} else if (Platform.isMacOS) {
|
|
||||||
var r = await gitFetchViaExecutable(
|
|
||||||
repoPath: repoPath,
|
|
||||||
privateKey: sshPrivateKey,
|
|
||||||
privateKeyPassword: sshPassword,
|
|
||||||
remoteName: remoteName,
|
|
||||||
);
|
|
||||||
if (r.isFailure) {
|
|
||||||
return fail(r);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var branchR = await gitDefaultBranchViaExecutable(
|
var branchNameR = await defaultBranchFn(
|
||||||
repoPath: repoPath,
|
repoPath, remoteName, sshPublicKey, sshPrivateKey, sshPassword);
|
||||||
privateKey: sshPrivateKey,
|
if (branchNameR.isFailure) {
|
||||||
privateKeyPassword: sshPassword,
|
return fail(branchNameR);
|
||||||
remoteName: remoteName,
|
|
||||||
);
|
|
||||||
if (r.isFailure) {
|
|
||||||
return fail(r);
|
|
||||||
}
|
|
||||||
remoteBranchName = branchR.getOrThrow();
|
|
||||||
}
|
}
|
||||||
|
var remoteBranchName = branchNameR.getOrThrow();
|
||||||
|
|
||||||
Log.i("Using remote branch: $remoteBranchName");
|
Log.i("Using remote branch: $remoteBranchName");
|
||||||
|
|
||||||
var branches = await repo.branches().getOrThrow();
|
var branches = await repo.branches().getOrThrow();
|
||||||
@ -145,16 +143,10 @@ Future<Result<void>> cloneRemote({
|
|||||||
var remoteBranchR = await repo.remoteBranch(remoteName, remoteBranchName);
|
var remoteBranchR = await repo.remoteBranch(remoteName, remoteBranchName);
|
||||||
if (remoteBranchR.isSuccess) {
|
if (remoteBranchR.isSuccess) {
|
||||||
Log.i("Merging '$remoteName/$remoteBranchName'");
|
Log.i("Merging '$remoteName/$remoteBranchName'");
|
||||||
if (Platform.isAndroid || Platform.isIOS) {
|
var r = await gitMergeFn(
|
||||||
await _gitRepo.merge(
|
repoPath, remoteName, remoteBranchName, authorName, authorEmail);
|
||||||
branch: '$remoteName/$remoteBranchName',
|
if (r.isFailure) {
|
||||||
authorName: authorName,
|
return fail(r);
|
||||||
authorEmail: authorEmail,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
var repo = await GitRepository.load(repoPath).getOrThrow();
|
|
||||||
var author = GitAuthor(name: authorName, email: authorEmail);
|
|
||||||
repo.mergeCurrentTrackingBranch(author: author).throwOnError();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -166,16 +158,10 @@ Future<Result<void>> cloneRemote({
|
|||||||
await repo.setUpstreamTo(remote, remoteBranchName).getOrThrow();
|
await repo.setUpstreamTo(remote, remoteBranchName).getOrThrow();
|
||||||
|
|
||||||
Log.i("Merging '$remoteName/$remoteBranchName'");
|
Log.i("Merging '$remoteName/$remoteBranchName'");
|
||||||
if (Platform.isAndroid || Platform.isIOS) {
|
var r = await gitMergeFn(
|
||||||
await _gitRepo.merge(
|
repoPath, remoteName, remoteBranchName, authorName, authorEmail);
|
||||||
branch: '$remoteName/$remoteBranchName',
|
if (r.isFailure) {
|
||||||
authorName: authorName,
|
return fail(r);
|
||||||
authorEmail: authorEmail,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
var repo = await GitRepository.load(repoPath).getOrThrow();
|
|
||||||
var author = GitAuthor(name: authorName, email: authorEmail);
|
|
||||||
repo.mergeCurrentTrackingBranch(author: author).throwOnError();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -191,36 +177,6 @@ Future<Result<void>> cloneRemote({
|
|||||||
return Result(null);
|
return Result(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> _remoteDefaultBranch({
|
|
||||||
required GitRepository repo,
|
|
||||||
required git_bindings.GitRepo libGit2Repo,
|
|
||||||
required String remoteName,
|
|
||||||
required String sshPublicKey,
|
|
||||||
required String sshPrivateKey,
|
|
||||||
required String sshPassword,
|
|
||||||
}) async {
|
|
||||||
try {
|
|
||||||
var branch = await libGit2Repo.defaultBranch(
|
|
||||||
remote: remoteName,
|
|
||||||
publicKey: sshPublicKey,
|
|
||||||
privateKey: sshPrivateKey,
|
|
||||||
password: sshPassword,
|
|
||||||
);
|
|
||||||
Log.i("Got default branch: $branch");
|
|
||||||
if (branch != null && branch.isNotEmpty) {
|
|
||||||
return branch;
|
|
||||||
}
|
|
||||||
} catch (ex) {
|
|
||||||
Log.w("Could not fetch git main branch", ex: ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
var remoteBranch = await repo.guessRemoteHead(remoteName);
|
|
||||||
if (remoteBranch == null) {
|
|
||||||
return 'master';
|
|
||||||
}
|
|
||||||
return remoteBranch.target!.branchName()!;
|
|
||||||
}
|
|
||||||
|
|
||||||
String folderNameFromCloneUrl(String cloneUrl) {
|
String folderNameFromCloneUrl(String cloneUrl) {
|
||||||
var name = p.basename(cloneUrl);
|
var name = p.basename(cloneUrl);
|
||||||
if (name.endsWith('.git')) {
|
if (name.endsWith('.git')) {
|
||||||
|
8
lib/setup/clone_auto_select.dart
Normal file
8
lib/setup/clone_auto_select.dart
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import 'dart:io' show Platform;
|
||||||
|
|
||||||
|
import 'clone_gitExec.dart' as git_exec;
|
||||||
|
import 'clone_libgit2.dart' as libgit2;
|
||||||
|
|
||||||
|
final isMobileApp = Platform.isIOS || Platform.isAndroid;
|
||||||
|
|
||||||
|
var cloneRemote = isMobileApp ? libgit2.cloneRemote : git_exec.cloneRemote;
|
79
lib/setup/clone_gitExec.dart
Normal file
79
lib/setup/clone_gitExec.dart
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import 'package:dart_git/dart_git.dart';
|
||||||
|
import 'package:dart_git/utils/result.dart';
|
||||||
|
import 'package:function_types/function_types.dart';
|
||||||
|
|
||||||
|
import 'package:gitjournal/utils/git_desktop.dart';
|
||||||
|
import 'clone.dart';
|
||||||
|
|
||||||
|
Future<Result<void>> cloneRemote({
|
||||||
|
required String repoPath,
|
||||||
|
required String cloneUrl,
|
||||||
|
required String remoteName,
|
||||||
|
required String sshPublicKey,
|
||||||
|
required String sshPrivateKey,
|
||||||
|
required String sshPassword,
|
||||||
|
required String authorName,
|
||||||
|
required String authorEmail,
|
||||||
|
required Func1<GitTransferProgress, void> progressUpdate,
|
||||||
|
}) =>
|
||||||
|
cloneRemotePluggable(
|
||||||
|
repoPath: repoPath,
|
||||||
|
cloneUrl: cloneUrl,
|
||||||
|
remoteName: remoteName,
|
||||||
|
sshPublicKey: sshPublicKey,
|
||||||
|
sshPrivateKey: sshPrivateKey,
|
||||||
|
sshPassword: sshPassword,
|
||||||
|
authorName: authorName,
|
||||||
|
authorEmail: authorEmail,
|
||||||
|
progressUpdate: progressUpdate,
|
||||||
|
gitFetchFn: _fetch,
|
||||||
|
defaultBranchFn: _defaultBranch,
|
||||||
|
gitMergeFn: _merge,
|
||||||
|
);
|
||||||
|
|
||||||
|
Future<Result<void>> _fetch(
|
||||||
|
String repoPath,
|
||||||
|
String remoteName,
|
||||||
|
String sshPublicKey,
|
||||||
|
String sshPrivateKey,
|
||||||
|
String sshPassword,
|
||||||
|
String statusFile,
|
||||||
|
) {
|
||||||
|
// FIXME: Stop ignoring the statusFile
|
||||||
|
return gitFetchViaExecutable(
|
||||||
|
repoPath: repoPath,
|
||||||
|
privateKey: sshPrivateKey,
|
||||||
|
privateKeyPassword: sshPassword,
|
||||||
|
remoteName: remoteName,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Result<String>> _defaultBranch(
|
||||||
|
String repoPath,
|
||||||
|
String remoteName,
|
||||||
|
String sshPublicKey,
|
||||||
|
String sshPrivateKey,
|
||||||
|
String sshPassword,
|
||||||
|
) {
|
||||||
|
return gitDefaultBranchViaExecutable(
|
||||||
|
repoPath: repoPath,
|
||||||
|
privateKey: sshPrivateKey,
|
||||||
|
privateKeyPassword: sshPassword,
|
||||||
|
remoteName: remoteName,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Result<void>> _merge(
|
||||||
|
String repoPath,
|
||||||
|
String remoteName,
|
||||||
|
String remoteBranchName,
|
||||||
|
String authorName,
|
||||||
|
String authorEmail,
|
||||||
|
) {
|
||||||
|
return catchAll(() async {
|
||||||
|
var repo = await GitRepository.load(repoPath).getOrThrow();
|
||||||
|
var author = GitAuthor(name: authorName, email: authorEmail);
|
||||||
|
await repo.mergeCurrentTrackingBranch(author: author).throwOnError();
|
||||||
|
return Result(null);
|
||||||
|
});
|
||||||
|
}
|
102
lib/setup/clone_libgit2.dart
Normal file
102
lib/setup/clone_libgit2.dart
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
import 'package:dart_git/utils/result.dart';
|
||||||
|
import 'package:function_types/function_types.dart';
|
||||||
|
import 'package:git_bindings/git_bindings.dart' as git_bindings;
|
||||||
|
|
||||||
|
import 'clone.dart';
|
||||||
|
|
||||||
|
Future<Result<void>> cloneRemote({
|
||||||
|
required String repoPath,
|
||||||
|
required String cloneUrl,
|
||||||
|
required String remoteName,
|
||||||
|
required String sshPublicKey,
|
||||||
|
required String sshPrivateKey,
|
||||||
|
required String sshPassword,
|
||||||
|
required String authorName,
|
||||||
|
required String authorEmail,
|
||||||
|
required Func1<GitTransferProgress, void> progressUpdate,
|
||||||
|
}) =>
|
||||||
|
cloneRemotePluggable(
|
||||||
|
repoPath: repoPath,
|
||||||
|
cloneUrl: cloneUrl,
|
||||||
|
remoteName: remoteName,
|
||||||
|
sshPublicKey: sshPublicKey,
|
||||||
|
sshPrivateKey: sshPrivateKey,
|
||||||
|
sshPassword: sshPassword,
|
||||||
|
authorName: authorName,
|
||||||
|
authorEmail: authorEmail,
|
||||||
|
progressUpdate: progressUpdate,
|
||||||
|
gitFetchFn: _fetch,
|
||||||
|
defaultBranchFn: _defaultBranch,
|
||||||
|
gitMergeFn: _merge,
|
||||||
|
);
|
||||||
|
|
||||||
|
Future<Result<void>> _fetch(
|
||||||
|
String repoPath,
|
||||||
|
String remoteName,
|
||||||
|
String sshPublicKey,
|
||||||
|
String sshPrivateKey,
|
||||||
|
String sshPassword,
|
||||||
|
String statusFile,
|
||||||
|
) async {
|
||||||
|
try {
|
||||||
|
var gitRepo = git_bindings.GitRepo(folderPath: repoPath);
|
||||||
|
await gitRepo.fetch(
|
||||||
|
remote: remoteName,
|
||||||
|
publicKey: sshPublicKey,
|
||||||
|
privateKey: sshPrivateKey,
|
||||||
|
password: sshPassword,
|
||||||
|
statusFile: statusFile,
|
||||||
|
);
|
||||||
|
} on Exception catch (e, st) {
|
||||||
|
return Result.fail(e, st);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Result<String>> _defaultBranch(
|
||||||
|
String repoPath,
|
||||||
|
String remoteName,
|
||||||
|
String sshPublicKey,
|
||||||
|
String sshPrivateKey,
|
||||||
|
String sshPassword,
|
||||||
|
) async {
|
||||||
|
try {
|
||||||
|
var gitRepo = git_bindings.GitRepo(folderPath: repoPath);
|
||||||
|
var branch = await gitRepo.defaultBranch(
|
||||||
|
remote: remoteName,
|
||||||
|
publicKey: sshPublicKey,
|
||||||
|
privateKey: sshPrivateKey,
|
||||||
|
password: sshPassword,
|
||||||
|
);
|
||||||
|
if (branch != null && branch.isNotEmpty) {
|
||||||
|
return Result(branch);
|
||||||
|
}
|
||||||
|
} on Exception catch (e, st) {
|
||||||
|
return Result.fail(e, st);
|
||||||
|
}
|
||||||
|
|
||||||
|
var ex = Exception("No Remote Branch found");
|
||||||
|
return Result.fail(ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Result<void>> _merge(
|
||||||
|
String repoPath,
|
||||||
|
String remoteName,
|
||||||
|
String remoteBranchName,
|
||||||
|
String authorName,
|
||||||
|
String authorEmail,
|
||||||
|
) async {
|
||||||
|
try {
|
||||||
|
var gitRepo = git_bindings.GitRepo(folderPath: repoPath);
|
||||||
|
await gitRepo.merge(
|
||||||
|
branch: '$remoteName/$remoteBranchName',
|
||||||
|
authorName: authorName,
|
||||||
|
authorEmail: authorEmail,
|
||||||
|
);
|
||||||
|
} on Exception catch (e, st) {
|
||||||
|
return Result.fail(e, st);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result(null);
|
||||||
|
}
|
@ -20,6 +20,7 @@ import 'package:gitjournal/settings/settings.dart';
|
|||||||
import 'package:gitjournal/setup/autoconfigure.dart';
|
import 'package:gitjournal/setup/autoconfigure.dart';
|
||||||
import 'package:gitjournal/setup/button.dart';
|
import 'package:gitjournal/setup/button.dart';
|
||||||
import 'package:gitjournal/setup/clone.dart';
|
import 'package:gitjournal/setup/clone.dart';
|
||||||
|
import 'package:gitjournal/setup/clone_auto_select.dart';
|
||||||
import 'package:gitjournal/setup/clone_url.dart';
|
import 'package:gitjournal/setup/clone_url.dart';
|
||||||
import 'package:gitjournal/setup/loading_error.dart';
|
import 'package:gitjournal/setup/loading_error.dart';
|
||||||
import 'package:gitjournal/setup/repo_selector.dart';
|
import 'package:gitjournal/setup/repo_selector.dart';
|
||||||
@ -566,7 +567,7 @@ class GitHostSetupScreenState extends State<GitHostSetupScreen> {
|
|||||||
_cloneProgress = p;
|
_cloneProgress = p;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
);
|
).throwOnError();
|
||||||
} on Exception catch (e, stacktrace) {
|
} on Exception catch (e, stacktrace) {
|
||||||
Log.e("Failed to clone", ex: e, stacktrace: stacktrace);
|
Log.e("Failed to clone", ex: e, stacktrace: stacktrace);
|
||||||
error = e.toString();
|
error = e.toString();
|
||||||
|
Reference in New Issue
Block a user