Skip to content

Commit

Permalink
xlsx merge
Browse files Browse the repository at this point in the history
  • Loading branch information
JonasLukasczyk committed Aug 20, 2024
1 parent d34099a commit e59c282
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 29 deletions.
42 changes: 26 additions & 16 deletions packages/main/src/GitService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import {
BrowserWindow
} from 'electron';
import { spawn } from 'child_process';
import FS from 'fs';
import {LocalFileSystemService} from './LocalFileSystemService.ts';

const PATH = require('path');

Expand All @@ -28,22 +30,30 @@ export const GitService = {

let error = false;
let output = '';
const handleOutput = data => {
if(!data) return;
let dataAsString = data.toString();
dataAsString = dataAsString.replace(/\n|\r/g, '\n');

output += dataAsString;
if(dataAsString.toLowerCase().includes('error'))
error = true;

for(let row of dataAsString.split('\n')){
if(row==='') continue;
window.webContents.send('GitService.MSG', row);
}
};
p.stdout.on('data', handleOutput);
p.stderr.on('data', handleOutput);
if(o.pipe) {
console.log(o.pipe)
LocalFileSystemService.enforcePath(null,o.pipe.split('/').slice(0,-1).join('/'));
const write_stream = FS.createWriteStream(o.pipe, {flags: 'w'});
p.stdout.pipe(write_stream);
p.stderr.pipe(write_stream);
} else {
const handleOutput = data => {
if(!data) return;
let dataAsString = data.toString();
dataAsString = dataAsString.replace(/\n|\r/g, '\n');

output += dataAsString;
if(dataAsString.toLowerCase().includes('error'))
error = true;

for(let row of dataAsString.split('\n')){
if(row==='' || o.silent) continue;
window.webContents.send('GitService.MSG', row);
}
};
p.stdout.on('data', handleOutput);
p.stderr.on('data', handleOutput);
}

// This hits when the child process cannot be spawned
p.on('error', err => {
Expand Down
7 changes: 5 additions & 2 deletions packages/renderer/src/ArcControlService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,14 @@ let init: {
arc_root: undefined | string ,
busy: boolean,
arc: null | ARC,
git_initialized: boolean
git_initialized: boolean,
skip_fs_updates: boolean,
} = {
arc_root: undefined ,
busy: false,
arc: null,
git_initialized: false
git_initialized: false,
skip_fs_updates: false
}

function relative_to_absolute_path(relativePath: string) {
Expand Down Expand Up @@ -221,6 +223,7 @@ const ArcControlService = {
},

updateARCfromFS: async ([path,type]) => {
if(ArcControlService.props.skip_fs_updates) return;
// track add/rm assays/studies through file explorer
const requires_update = path.includes('isa.assay.xlsx') || path.includes('isa.study.xlsx');
if(!requires_update) return;
Expand Down
6 changes: 4 additions & 2 deletions packages/renderer/src/dialogs/GitDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import { ref, reactive, onMounted, onUnmounted } from 'vue';
export interface Props {
title: String,
ok_title: String,
ok_icon?: String,
cancel_title: String,
cancel_icon?: String,
state: Number,
};
const props = defineProps<Props>();
Expand Down Expand Up @@ -78,8 +80,8 @@ const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } = useDialogPluginC
<q-linear-progress :value='100' :indeterminate='props.state===0' color="secondary" class="q-mt-sm" track-color="grey-3" />

<q-card-actions align="right" style="margin:0 1.5em 1.5em;">
<q-btn color="secondary" :loading='props.state===0' :disabled='props.state===0 || (props.state===2 && props.cancel_title)' :label="props.ok_title" @click="onDialogOK" type='submit' class='text-weight-bold'/>
<q-btn v-if='props.cancel_title' color="secondary" :disabled='props.state===0' :label="props.cancel_title" @click="onDialogCancel" type='submit' class='text-weight-bold'/>
<q-btn color="secondary" :loading='props.state===0' :disabled='props.state===0 || (props.state===2 && props.cancel_title)' :label="props.ok_title" @click="onDialogOK" type='submit' class='text-weight-bold' :icon='props.ok_icon || "check_circle"'/>
<q-btn v-if='props.cancel_title' color="secondary" :disabled='props.state===0' :label="props.cancel_title" @click="onDialogCancel" type='submit' class='text-weight-bold' :icon='props.cancel_icon || "cancel"'/>
</q-card-actions>
</q-card>

Expand Down
13 changes: 6 additions & 7 deletions packages/renderer/src/views/GitCommitView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ const getStatus = async()=>{
const sizes = await window.ipc.invoke('LocalFileSystemService.getFileSizes', status.map(x=> ArcControlService.props.arc_root +'/'+x[1]));
for(let i in sizes)
status[i].push(sizes[i]);
console.log(status)
iProps.git_status = status;
};
Expand Down Expand Up @@ -273,7 +274,7 @@ onUnmounted(()=>{
<q-list dense>
<q-item>
<a_tooltip>
Here the changes to all files in your ARC are displayed together with the file size:<br>
Here the changes to all files in your ARC are displayed together with the file size:<br>
<ul>
<li><q-icon color='secondary' name='inventory'/>: No changes</li>
<li><q-icon name="edit_square" color="secondary" />: The file was edited</li>
Expand All @@ -283,9 +284,9 @@ onUnmounted(()=>{
</a_tooltip>
<q-item-section>
<q-item-label style="font-weight:bold;font-size:1em;">Changes</q-item-label>
</q-item-section>
</q-item-section>
</q-item>

<q-item v-if='iProps.git_status.length<1'>
<q-item-section avatar>
<q-icon color='secondary' name='inventory'></q-icon>
Expand Down Expand Up @@ -313,13 +314,11 @@ onUnmounted(()=>{
Click RESET to undo your latest changes and convert the ARC to the last saved commit
</a_tooltip>
</a_btn>
<a_btn label="Commit" @click="commit" icon='check_circle' :disabled='iProps.git_status.length<1'/>

<!-- <a_btn label="Commit" @click="commit" icon='check_circle' :disabled='iProps.git_status.length'>
<a_btn label="Commit" @click="commit" icon='check_circle' :disabled='iProps.git_status.length<1'>
<a_tooltip>
Once ready, click COMMIT to store your changes locally
</a_tooltip>
</a_btn> -->
</a_btn>
</q-card-actions>

</q-card>
Expand Down
133 changes: 131 additions & 2 deletions packages/renderer/src/views/GitSyncView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import ArcControlService from '../ArcControlService.ts';
import AppProperties from '../AppProperties.ts';
import { Xlsx } from '@fslab/fsspreadsheet/Xlsx.js';
import { Json } from '@fslab/fsspreadsheet/Json.js';
import AddRemoteDialog from '../dialogs/AddRemoteDialog.vue';
import GitDialog from '../dialogs/GitDialog.vue';
import ConfirmationDialog from '../dialogs/ConfirmationDialog.vue';
Expand Down Expand Up @@ -145,6 +148,120 @@ const push = async()=>{
dialogProps.state=1;
};
const merge_xlsx = async (rel_path,pointers) => {
for(const pointer of pointers){
const path = `/tmp/arc_merge/${pointer}/${rel_path}`;
// get xlsx file
await window.ipc.invoke('GitService.run', {
args: [`show`,`${pointer}:${rel_path}`],
cwd: ArcControlService.props.arc_root,
pipe: path
});
// write json representation
const buffer = await window.ipc.invoke('LocalFileSystemService.readFile', [path,{}]);
const wb = await Xlsx.fromBytes(buffer);
const json = Json.toRowsJsonString(wb,1,true);
await window.ipc.invoke('LocalFileSystemService.writeFile', [path+'.json',json]);
}
// merge json representations
const merged_json = await window.ipc.invoke('GitService.run', {
args: [`merge-file`,`-p`,
`/tmp/arc_merge/${pointers[0]}/${rel_path}.json`,
`/tmp/arc_merge/${pointers[1]}/${rel_path}.json`,
`/tmp/arc_merge/${pointers[2]}/${rel_path}.json`,
],
cwd: ArcControlService.props.arc_root,
silent: true
});
// write merged workbooks
const merged_wb = Json.fromRowsJsonString(merged_json[1]);
const merged_buffer = await Xlsx.toBytes(merged_wb);
await window.ipc.invoke(
'LocalFileSystemService.writeFile',
[
`${ArcControlService.props.arc_root}/${rel_path}`,
merged_buffer,
{}
]
);
await window.ipc.invoke('GitService.run', {
args: [`add`,rel_path],
cwd: ArcControlService.props.arc_root
});
};
const merge = async ()=>{
const dialogProps = reactive({
title: 'Merge Divergent Branches',
ok_title: 'Ok',
cancel_title: 'Cancel',
state: 0,
});
$q.dialog({
component: GitDialog,
componentProps: dialogProps
}).onOk(async ()=>{
await ArcControlService.readARC();
await checkRemotes();
}).onCancel(async ()=>{
const rebase = await window.ipc.invoke('GitService.run', {
args: [`reset`,`--hard`,`ORIG_HEAD`],
cwd: ArcControlService.props.arc_root
});
});
const branches = await getBranches();
if(!branches.current) return;
// clean helper function
const clean = x => x[1].split('\n')[0].trim();
// get latest commit hash
const local_commit = clean(await window.ipc.invoke('GitService.run', {
args: [`rev-parse`,`HEAD`],
cwd: ArcControlService.props.arc_root
}));
// get common ancestor
const common_ancestor = clean(await window.ipc.invoke('GitService.run', {
args: [`merge-base`,`HEAD`,iProps.remote+'/'+branches.current],
cwd: ArcControlService.props.arc_root
}));
// initiate rebase
const rebase = await window.ipc.invoke('GitService.run', {
args: [`rebase`,iProps.remote+'/'+branches.current],
cwd: ArcControlService.props.arc_root
});
// get conflicts
const conflicts = rebase[1].split('\n').filter(r=>r.startsWith('CONFLICT')).map(r=>r.split('Merge conflict in ').pop());
// attempt to resolve conflicts
ArcControlService.props.skip_fs_updates = true;
for(let file of conflicts.filter(f=>f.endsWith('.xlsx')))
await merge_xlsx(file,[local_commit,common_ancestor,iProps.remote+'/'+branches.current]);
ArcControlService.props.skip_fs_updates = false;
await window.ipc.invoke('GitService.run', {
args: [`commit`,'--ammend'],
cwd: ArcControlService.props.arc_root
});
await window.ipc.invoke('GitService.run', {
args: [`-c`,`core.editor=true`,`rebase`,'--continue'],
cwd: ArcControlService.props.arc_root
});
dialogProps.state=1;
};
const pull = async()=>{
await getStatus();
if(iProps.git_status.length>0)
Expand All @@ -155,12 +272,18 @@ const pull = async()=>{
ok_title: 'Ok',
cancel_title: null,
state: 0,
needs_merge: false
});
$q.dialog({
component: GitDialog,
componentProps: dialogProps
}).onOk(async ()=>{
if(dialogProps.needs_merge)
await merge();
else
await checkRemotes();
}).onCancel(async ()=>{
await checkRemotes();
});
Expand All @@ -187,6 +310,12 @@ const pull = async()=>{
GIT_LFS_SKIP_SMUDGE: iProps.use_lfs?0:1
}
});
dialogProps.needs_merge = !response[0] && response[1].includes('You have divergent branches and need to specify how to reconcile them.');
if(dialogProps.needs_merge){
dialogProps.ok_title = 'Merge';
dialogProps.cancel_title = 'Cancel';
dialogProps.ok_icon = 'merge';
}
if(iProps.use_lfs){
response = await window.ipc.invoke('GitService.run', {
args: [`lfs`,`pull`,iProps.remote,branches.current],
Expand Down Expand Up @@ -356,7 +485,7 @@ const inspectArc = url =>{
</q-item-section>
</q-item>

<a_tooltip>
<a_tooltip>
Managing remotes
<ul>
<li><q-icon name="add_circle" color="secondary" />: Add additional remote connections</li>
Expand All @@ -378,7 +507,7 @@ const inspectArc = url =>{
<a_tooltip>
<div>
Check this box to synchronize large files
</div>
</div>
<q-icon name="warning" color="grey-3" size="1.5em"/>
Data up- and downloads taking more than one hour require a personal access token.<br>
Click <q-icon name="add_circle" color="secondary" /> to add a remote with a personal access token.
Expand Down

0 comments on commit e59c282

Please sign in to comment.