Improve repo migrations

Earlier we had one folder 'journal_local', when the remote would be
setup a new folder called 'journal' would be created, and each all the
files would be copied over. This meant the local history was being
destroyed.

Now, we only have 1 folder 'journal', and on 'cloning', we add the url
as a remote, and do a git fetch + merge.

This simplifies everything drastically, and opens the door for multiple
remotes.
This commit is contained in:
Vishesh Handa
2020-10-15 12:07:35 +02:00
parent 0839b8e38a
commit 540078c413
8 changed files with 93 additions and 139 deletions

View File

@ -1,49 +0,0 @@
import 'dart:async';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:git_bindings/git_bindings.dart';
import 'package:path/path.dart' as p;
import 'package:gitjournal/utils/logger.dart';
//
// FIXME: This isn't ideal as we are skipping all the edits / deletes
//
Future migrateGitRepo({
@required String gitBasePath,
@required String fromGitBasePath,
@required String toGitBaseFolder,
@required String gitAuthor,
@required String gitAuthorEmail,
}) async {
Log.d("migrateGitRepo $fromGitBasePath $toGitBaseFolder");
var fromBasePath = p.join(gitBasePath, fromGitBasePath);
var toGitRepoPath = p.join(gitBasePath, toGitBaseFolder);
Log.d("toGitRemotePath $toGitRepoPath");
final dir = Directory(fromBasePath);
var lister = dir.list(recursive: false);
await for (var fileEntity in lister) {
if (fileEntity is! File) {
continue;
}
File file = fileEntity;
var fileName = p.basename(file.path);
var toPath = p.join(toGitRepoPath, fileName);
Log.d("Migrating " + file.path + " --> " + toPath);
await file.copy(toPath);
var gitRepo = GitRepo(folderPath: toGitRepoPath);
await gitRepo.add(fileName);
await gitRepo.commit(
message: "Added Note",
authorEmail: gitAuthorEmail,
authorName: gitAuthor,
);
}
Log.d("migrateGitRepo: Done");
}

View File

@ -67,12 +67,16 @@ class JournalApp extends StatefulWidget {
}
Log.i('-----');
await settings.migrate(pref, appState.gitBaseDirectory);
// FIXME: This can be replaced with a fs stat
if (settings.localGitRepoConfigured == false) {
settings.internalRepoFolderName = "journal";
// FIXME: What about exceptions!
settings.localGitRepoFolderName = "journal_local";
var repoPath = p.join(
appState.gitBaseDirectory,
settings.localGitRepoFolderName,
settings.internalRepoFolderName,
);
await GitRepository.init(repoPath);
@ -381,8 +385,9 @@ class _JournalAppState extends State<JournalApp> {
return SettingsScreen();
case '/setupRemoteGit':
return GitHostSetupScreen(
"journal",
stateContainer.completeGitHostSetup,
repoFolderName: settings.internalRepoFolderName,
remoteName: "origin",
onCompletedFunction: stateContainer.completeGitHostSetup,
);
case '/onBoarding':
return OnBoardingScreen();

View File

@ -180,14 +180,18 @@ class GitNoteRepository {
Future<void> merge() async {
var repo = await git.GitRepository.load(gitDirPath);
var branch = await repo.currentBranch();
if (branch == null) {
var branchConfig = repo.config.branch(branch);
if (branchConfig == null) {
logExceptionWarning(Exception("Current Branch null"), StackTrace.current);
return;
}
assert(branchConfig.name != null);
assert(branchConfig.merge != null);
try {
await _gitRepo.merge(
branch: branch.remoteTrackingBranch(),
branch: branchConfig.remoteTrackingBranch(),
authorEmail: settings.gitAuthorEmail,
authorName: settings.gitAuthor,
);

View File

@ -148,8 +148,9 @@ class _GitRemoteSettingsScreenState extends State<GitRemoteSettingsScreen> {
var route = MaterialPageRoute(
builder: (context) => GitHostSetupScreen(
repoFolderName,
stateContainer.completeGitHostSetup,
repoFolderName: repoFolderName,
remoteName: 'origin',
onCompletedFunction: stateContainer.completeGitHostSetup,
),
settings: const RouteSettings(name: '/setupRemoteGit'),
);

View File

@ -1,12 +1,16 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:collection/collection.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:path/path.dart' as p;
import 'package:shared_preferences/shared_preferences.dart';
import 'package:gitjournal/core/sorting_mode.dart';
import 'package:gitjournal/folder_views/common.dart';
import 'package:gitjournal/screens/note_editor.dart';
import 'package:gitjournal/utils/logger.dart';
class Settings extends ChangeNotifier {
Settings();
@ -34,7 +38,7 @@ class Settings extends ChangeNotifier {
SettingsFolderViewType defaultView = SettingsFolderViewType.Default;
bool showNoteSummary = true;
String folderViewHeaderType = "TitleGenerated";
int version = 0;
int version = 1;
SettingsHomeScreen homeScreen = SettingsHomeScreen.Default;
@ -56,10 +60,9 @@ class Settings extends ChangeNotifier {
bool bottomMenuBar = false;
// From AppState
String localGitRepoFolderName = "";
String internalRepoFolderName = "";
bool localGitRepoConfigured = false;
String remoteGitRepoFolderName = "";
bool remoteGitRepoConfigured = false;
bool storeInternally = true;
@ -131,8 +134,7 @@ class Settings extends ChangeNotifier {
// From AppState
localGitRepoConfigured = pref.getBool("localGitRepoConfigured") ?? false;
remoteGitRepoConfigured = pref.getBool("remoteGitRepoConfigured") ?? false;
localGitRepoFolderName = pref.getString("localGitRepoPath") ?? "";
remoteGitRepoFolderName = pref.getString("remoteGitRepoPath") ?? "";
internalRepoFolderName = pref.getString("remoteGitRepoPath") ?? "";
bottomMenuBar = pref.getBool("bottomMenuBar") ?? bottomMenuBar;
storeInternally = pref.getBool("storeInternally") ?? storeInternally;
@ -221,8 +223,7 @@ class Settings extends ChangeNotifier {
pref.setBool("localGitRepoConfigured", localGitRepoConfigured);
pref.setBool("remoteGitRepoConfigured", remoteGitRepoConfigured);
pref.setString("localGitRepoPath", localGitRepoFolderName);
pref.setString("remoteGitRepoPath", remoteGitRepoFolderName);
pref.setString("remoteGitRepoPath", internalRepoFolderName);
notifyListeners();
}
@ -302,8 +303,7 @@ class Settings extends ChangeNotifier {
'emojiParser': emojiParser.toString(),
'localGitRepoConfigured': localGitRepoConfigured.toString(),
'remoteGitRepoConfigured': remoteGitRepoConfigured.toString(),
'localGitRepoFolderName': localGitRepoFolderName.toString(),
'remoteGitRepoFolderName': remoteGitRepoFolderName.toString(),
'remoteGitRepoPath': internalRepoFolderName.toString(),
'bottomMenuBar': bottomMenuBar.toString(),
'storeInternally': storeInternally.toString(),
'storageLocation': storageLocation,
@ -317,6 +317,24 @@ class Settings extends ChangeNotifier {
m.remove("defaultNewNoteFolderSpec");
return m;
}
Future<void> migrate(SharedPreferences pref, String gitBaseDir) async {
if (version == 0) {
if (localGitRepoConfigured && !remoteGitRepoConfigured) {
Log.i("Migrating from local and remote repos to a single one");
var oldName = p.join(gitBaseDir, "journal_local");
var newName = p.join(gitBaseDir, "journal");
await Directory(oldName).rename(newName);
internalRepoFolderName = "journal";
version = 1;
pref.setInt("settingsVersion", version);
pref.setString('remoteGitRepoPath', internalRepoFolderName);
return;
}
}
}
}
class NoteFileNameFormat {

View File

@ -3,10 +3,11 @@ import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:dart_git/git.dart';
import 'package:dots_indicator/dots_indicator.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:function_types/function_types.dart';
import 'package:git_bindings/git_bindings.dart';
import 'package:git_bindings/git_bindings.dart' as git_bindings;
import 'package:path/path.dart' as p;
import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher.dart';
@ -27,9 +28,14 @@ import 'package:gitjournal/utils/logger.dart';
class GitHostSetupScreen extends StatefulWidget {
final String repoFolderName;
final Func1<String, void> onCompletedFunction;
final String remoteName;
final Func2<String, String, void> onCompletedFunction;
GitHostSetupScreen(this.repoFolderName, this.onCompletedFunction);
GitHostSetupScreen({
@required this.repoFolderName,
@required this.remoteName,
@required this.onCompletedFunction,
});
@override
GitHostSetupScreenState createState() {
@ -203,7 +209,10 @@ class GitHostSetupScreenState extends State<GitHostSetupScreen> {
} else if (_keyGenerationChoice == KeyGenerationChoice.UserProvided) {
return GitHostUserProvidedKeys(
doneFunction: (String publicKey, String privateKey) async {
await setSshKeys(publicKey: publicKey, privateKey: privateKey);
await git_bindings.setSshKeys(
publicKey: publicKey,
privateKey: privateKey,
);
setState(() {
this.publicKey = publicKey;
_pageCount = pos + 2;
@ -284,7 +293,8 @@ class GitHostSetupScreenState extends State<GitHostSetupScreen> {
} else if (_keyGenerationChoice == KeyGenerationChoice.UserProvided) {
return GitHostUserProvidedKeys(
doneFunction: (String publicKey, String privateKey) async {
await setSshKeys(publicKey: publicKey, privateKey: privateKey);
await git_bindings.setSshKeys(
publicKey: publicKey, privateKey: privateKey);
setState(() {
this.publicKey = publicKey;
_pageCount = pos + 2;
@ -399,7 +409,7 @@ class GitHostSetupScreenState extends State<GitHostSetupScreen> {
"-" +
DateTime.now().toIso8601String().substring(0, 10); // only the date
generateSSHKeys(comment: comment).then((String publicKey) {
git_bindings.generateSSHKeys(comment: comment).then((String publicKey) {
setState(() {
this.publicKey = publicKey;
Log.d("PublicKey: " + publicKey);
@ -471,17 +481,21 @@ class GitHostSetupScreenState extends State<GitHostSetupScreen> {
var stateContainer = Provider.of<StateContainer>(context, listen: false);
var basePath = stateContainer.appState.gitBaseDirectory;
// Just in case it was half cloned because of an error
String repoPath = p.join(basePath, widget.repoFolderName);
await _removeExistingClone(repoPath);
var settings = Provider.of<Settings>(context, listen: false);
var repoName = settings.internalRepoFolderName;
var repoPath = p.join(basePath, repoName);
Log.i("RepoPath: $repoPath");
String error;
try {
Log.d("Cloning " + _gitCloneUrl);
await GitRepo.clone(repoPath, _gitCloneUrl);
} on GitException catch (e) {
var repo = await GitRepository.load(repoPath);
await repo.addRemote(widget.remoteName, _gitCloneUrl);
var repoN = git_bindings.GitRepo(folderPath: repoPath);
await repoN.fetch(widget.remoteName);
} on Exception catch (e) {
Log.e(e.toString());
error = e.cause;
error = e.toString();
}
if (error != null && error.isNotEmpty) {
@ -507,9 +521,7 @@ class GitHostSetupScreenState extends State<GitHostSetupScreen> {
var ignoreFile = File(p.join(repoPath, ".gitignore"));
ignoreFile.createSync();
var repo = GitRepo(
folderPath: repoPath,
);
var repo = git_bindings.GitRepo(folderPath: repoPath);
await repo.add('.gitignore');
var settings = Provider.of<Settings>(context, listen: false);
@ -525,7 +537,7 @@ class GitHostSetupScreenState extends State<GitHostSetupScreen> {
parameters: _buildOnboardingAnalytics(),
);
Navigator.pop(context);
widget.onCompletedFunction(widget.repoFolderName);
widget.onCompletedFunction(widget.repoFolderName, widget.remoteName);
}
Future<void> _completeAutoConfigure() async {
@ -536,7 +548,7 @@ class GitHostSetupScreenState extends State<GitHostSetupScreen> {
setState(() {
_autoConfigureMessage = tr('setup.sshKey.generate');
});
var publicKey = await generateSSHKeys(comment: "GitJournal");
var publicKey = await git_bindings.generateSSHKeys(comment: "GitJournal");
Log.i("Adding as a deploy key");
_autoConfigureMessage = tr('setup.sshKey.addDeploy');
@ -594,17 +606,6 @@ class GitHostSetupScreenState extends State<GitHostSetupScreen> {
return map;
}
Future _removeExistingClone(String baseDirPath) async {
var baseDir = Directory(baseDirPath);
var dotGitDir = Directory(p.join(baseDir.path, ".git"));
bool exists = dotGitDir.existsSync();
if (exists) {
Log.d("Removing " + baseDir.path);
await baseDir.delete(recursive: true);
await baseDir.create();
}
}
}
class GitHostChoicePage extends StatelessWidget {

View File

@ -4,11 +4,11 @@ import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:dart_git/dart_git.dart';
import 'package:path/path.dart' as p;
import 'package:synchronized/synchronized.dart';
import 'package:gitjournal/analytics.dart';
import 'package:gitjournal/apis/git_migration.dart';
import 'package:gitjournal/appstate.dart';
import 'package:gitjournal/core/git_repo.dart';
import 'package:gitjournal/core/note.dart';
@ -40,22 +40,11 @@ class StateContainer with ChangeNotifier {
@required this.gitBaseDirectory,
}) {
assert(settings.localGitRepoConfigured);
String repoPath;
if (settings.remoteGitRepoConfigured) {
repoPath = p.join(gitBaseDirectory, settings.remoteGitRepoFolderName);
} else if (settings.localGitRepoConfigured) {
repoPath = p.join(gitBaseDirectory, settings.localGitRepoFolderName);
}
var repoPath = p.join(gitBaseDirectory, settings.internalRepoFolderName);
_gitRepo = GitNoteRepository(gitDirPath: repoPath, settings: settings);
appState.notesFolder = NotesFolderFS(null, _gitRepo.gitDirPath, settings);
// Just a fail safe
if (!settings.remoteGitRepoConfigured) {
removeExistingRemoteClone();
}
// Makes it easier to filter the analytics
getAnalytics().firebase.setUserProperty(
name: 'onboarded',
@ -81,17 +70,6 @@ class StateContainer with ChangeNotifier {
Log.i("Finished loading all the notes");
}
void removeExistingRemoteClone() async {
var remoteGitDir =
Directory(p.join(gitBaseDirectory, settings.remoteGitRepoFolderName));
var dotGitDir = Directory(p.join(remoteGitDir.path, ".git"));
bool exists = dotGitDir.existsSync();
if (exists) {
await remoteGitDir.delete(recursive: true);
}
}
Future<void> _loadNotes() async {
// FIXME: We should report the notes that failed to load
return _loadLock.synchronized(() async {
@ -351,29 +329,25 @@ class StateContainer with ChangeNotifier {
});
}
void completeGitHostSetup(String repoFolderName) {
// FIXME: Pass the remote name that was added
void completeGitHostSetup(String repoFolderName, String remoteName) {
() async {
var reconfiguringRemote = settings.remoteGitRepoConfigured;
var repo = await GitRepository.load(_gitRepo.gitDirPath);
var remote = repo.config.remote(remoteName);
var remoteBranchName = 'master';
// FIXME: How to get this?
//
// There is no way to get it, just need to iterate over the refs
// and look for one!
await repo.setUpstreamTo(remote, remoteBranchName);
// At this point the remote should have been added and fetched
await _gitRepo.merge();
settings.remoteGitRepoConfigured = true;
settings.remoteGitRepoFolderName = repoFolderName;
if (!reconfiguringRemote) {
await migrateGitRepo(
fromGitBasePath: settings.localGitRepoFolderName,
toGitBaseFolder: settings.remoteGitRepoFolderName,
gitBasePath: gitBaseDirectory,
gitAuthor: settings.gitAuthor,
gitAuthorEmail: settings.gitAuthorEmail,
);
}
var repoPath = p.join(gitBaseDirectory, settings.remoteGitRepoFolderName);
_gitRepo = GitNoteRepository(gitDirPath: repoPath, settings: settings);
appState.notesFolder.reset(_gitRepo.gitDirPath);
await _persistConfig();
await _notesCache.clear();
_loadNotes();
_syncNotes();

View File

@ -181,7 +181,7 @@ packages:
description:
path: "."
ref: HEAD
resolved-ref: "8e7e782f32f6cb1f39e39036d795f06ddecc56aa"
resolved-ref: aed5883fd17219c778cdf2d0d613a873d0b20f6c
url: "https://github.com/GitJournal/dart_git.git"
source: git
version: "0.0.1"
@ -226,7 +226,7 @@ packages:
name: equatable
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.1"
version: "1.2.5"
ext_storage:
dependency: "direct main"
description: