expo-tiddlywiki-filesystem-android-external-storage 2.3.0 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/src/main/java/expo/modules/externalstorage/ExternalStorageModule.kt +40 -0
- package/android/src/main/java/expo/modules/externalstorage/GitHelper.kt +451 -0
- package/build/index.d.ts +94 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js.map +1 -1
- package/ios/ExternalStorageModule.swift +40 -0
- package/ios/GitHelper.swift +63 -1
- package/package.json +1 -1
- package/src/index.ts +104 -0
|
@@ -584,6 +584,46 @@ class ExternalStorageModule : Module() {
|
|
|
584
584
|
GitHelper.gitReset(gitRootDir, ref, mode)
|
|
585
585
|
}
|
|
586
586
|
|
|
587
|
+
AsyncFunction("gitClone") { url: String, directory: String, branch: String?, depth: Int, singleBranch: Boolean, noTags: Boolean, headers: String? ->
|
|
588
|
+
GitHelper.gitClone(url, directory, branch, depth, singleBranch, noTags, headers)
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
AsyncFunction("gitLog") { gitRootDir: String, ref: String?, maxCount: Int ->
|
|
592
|
+
GitHelper.gitLog(gitRootDir, ref, maxCount)
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
AsyncFunction("gitResolveRef") { gitRootDir: String, ref: String ->
|
|
596
|
+
GitHelper.gitResolveRef(gitRootDir, ref)
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
AsyncFunction("gitCurrentBranch") { gitRootDir: String ->
|
|
600
|
+
GitHelper.gitCurrentBranch(gitRootDir)
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
AsyncFunction("gitInit") { directory: String, defaultBranch: String ->
|
|
604
|
+
GitHelper.gitInit(directory, defaultBranch)
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
AsyncFunction("gitSetConfig") { gitRootDir: String, section: String, subsection: String?, name: String, value: String ->
|
|
608
|
+
GitHelper.gitSetConfig(gitRootDir, section, subsection, name, value)
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
AsyncFunction("gitAddRemote") { gitRootDir: String, remoteName: String, url: String ->
|
|
612
|
+
GitHelper.gitAddRemote(gitRootDir, remoteName, url)
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
AsyncFunction("gitReadBlob") { gitRootDir: String, ref: String, filepath: String, asBase64: Boolean ->
|
|
616
|
+
GitHelper.gitReadBlob(gitRootDir, ref, filepath, asBase64)
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
AsyncFunction("gitDiffTrees") { gitRootDir: String, oldRef: String, newRef: String ->
|
|
620
|
+
GitHelper.gitDiffTrees(gitRootDir, oldRef, newRef)
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
AsyncFunction("gitDiscardFileChanges") { gitRootDir: String, filepath: String ->
|
|
624
|
+
GitHelper.gitDiscardFileChanges(gitRootDir, filepath)
|
|
625
|
+
}
|
|
626
|
+
|
|
587
627
|
// ─── TiddlyWiki batch file parsing ─────────────────────────────────
|
|
588
628
|
|
|
589
629
|
/**
|
|
@@ -17,6 +17,7 @@ import org.json.JSONArray
|
|
|
17
17
|
import org.json.JSONObject
|
|
18
18
|
import java.io.ByteArrayOutputStream
|
|
19
19
|
import java.io.File
|
|
20
|
+
import android.util.Base64
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
23
|
* All git-related operations using JGit — a pure Java implementation of Git.
|
|
@@ -548,4 +549,454 @@ internal object GitHelper {
|
|
|
548
549
|
}
|
|
549
550
|
return result.toString()
|
|
550
551
|
}
|
|
552
|
+
|
|
553
|
+
// ─── Git clone (JGit) ───────────────────────────────────────────
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* Clone a remote repository into the specified directory.
|
|
557
|
+
* Uses JGit's CloneCommand for efficient native pack handling.
|
|
558
|
+
*/
|
|
559
|
+
fun gitClone(
|
|
560
|
+
url: String,
|
|
561
|
+
directory: String,
|
|
562
|
+
branch: String?,
|
|
563
|
+
depth: Int,
|
|
564
|
+
singleBranch: Boolean,
|
|
565
|
+
noTags: Boolean,
|
|
566
|
+
headers: String?
|
|
567
|
+
): String {
|
|
568
|
+
val result = JSONObject()
|
|
569
|
+
try {
|
|
570
|
+
val targetDir = File(directory)
|
|
571
|
+
val cloneCommand = Git.cloneRepository()
|
|
572
|
+
.setURI(url)
|
|
573
|
+
.setDirectory(targetDir)
|
|
574
|
+
.setCloneAllBranches(!singleBranch)
|
|
575
|
+
|
|
576
|
+
if (singleBranch && branch != null) {
|
|
577
|
+
cloneCommand.setBranch("refs/heads/$branch")
|
|
578
|
+
cloneCommand.setBranchesToClone(listOf("refs/heads/$branch"))
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
if (depth > 0) {
|
|
582
|
+
cloneCommand.setDepth(depth)
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
if (noTags) {
|
|
586
|
+
cloneCommand.setNoTags()
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
applyHeaders(cloneCommand, headers)
|
|
590
|
+
|
|
591
|
+
val git = cloneCommand.call()
|
|
592
|
+
val headId = git.repository.resolve(Constants.HEAD)
|
|
593
|
+
|
|
594
|
+
result.put("ok", true)
|
|
595
|
+
result.put("head", headId?.name ?: "")
|
|
596
|
+
android.util.Log.i("GitClone", "Cloned $url to $directory, HEAD=${headId?.name?.take(8)}")
|
|
597
|
+
git.repository.close()
|
|
598
|
+
} catch (e: Exception) {
|
|
599
|
+
result.put("ok", false)
|
|
600
|
+
result.put("error", e.message ?: "Unknown clone error")
|
|
601
|
+
android.util.Log.e("GitClone", "Clone failed: ${e.message}", e)
|
|
602
|
+
}
|
|
603
|
+
return result.toString()
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// ─── Git log (JGit) ─────────────────────────────────────────────
|
|
607
|
+
|
|
608
|
+
/**
|
|
609
|
+
* Get commit history using JGit's LogCommand.
|
|
610
|
+
* Returns JSON array of commit objects.
|
|
611
|
+
*/
|
|
612
|
+
fun gitLog(
|
|
613
|
+
gitRootDir: String,
|
|
614
|
+
ref: String?,
|
|
615
|
+
maxCount: Int
|
|
616
|
+
): String {
|
|
617
|
+
val result = JSONObject()
|
|
618
|
+
try {
|
|
619
|
+
val repo = openRepo(gitRootDir)
|
|
620
|
+
try {
|
|
621
|
+
val git = Git(repo)
|
|
622
|
+
val logCommand = git.log()
|
|
623
|
+
|
|
624
|
+
if (ref != null && ref.isNotEmpty()) {
|
|
625
|
+
val resolved = repo.resolve(ref)
|
|
626
|
+
if (resolved != null) {
|
|
627
|
+
logCommand.add(resolved)
|
|
628
|
+
} else {
|
|
629
|
+
result.put("ok", true)
|
|
630
|
+
result.put("commits", JSONArray())
|
|
631
|
+
return result.toString()
|
|
632
|
+
}
|
|
633
|
+
} else {
|
|
634
|
+
logCommand.all()
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
logCommand.setMaxCount(maxCount)
|
|
638
|
+
|
|
639
|
+
val commits = JSONArray()
|
|
640
|
+
for (commit in logCommand.call()) {
|
|
641
|
+
val obj = JSONObject()
|
|
642
|
+
obj.put("oid", commit.id.name)
|
|
643
|
+
obj.put("message", commit.fullMessage)
|
|
644
|
+
obj.put("authorName", commit.authorIdent.name)
|
|
645
|
+
obj.put("authorEmail", commit.authorIdent.emailAddress)
|
|
646
|
+
obj.put("timestamp", commit.authorIdent.`when`.time)
|
|
647
|
+
val parents = JSONArray()
|
|
648
|
+
for (parent in commit.parents) {
|
|
649
|
+
parents.put(parent.id.name)
|
|
650
|
+
}
|
|
651
|
+
obj.put("parentOids", parents)
|
|
652
|
+
commits.put(obj)
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
result.put("ok", true)
|
|
656
|
+
result.put("commits", commits)
|
|
657
|
+
} finally {
|
|
658
|
+
repo.close()
|
|
659
|
+
}
|
|
660
|
+
} catch (e: Exception) {
|
|
661
|
+
result.put("ok", false)
|
|
662
|
+
result.put("error", e.message ?: "Unknown log error")
|
|
663
|
+
}
|
|
664
|
+
return result.toString()
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
// ─── Git resolve ref (JGit) ─────────────────────────────────────
|
|
668
|
+
|
|
669
|
+
/**
|
|
670
|
+
* Resolve a git reference to its SHA-1 hash.
|
|
671
|
+
*/
|
|
672
|
+
fun gitResolveRef(
|
|
673
|
+
gitRootDir: String,
|
|
674
|
+
ref: String
|
|
675
|
+
): String {
|
|
676
|
+
val result = JSONObject()
|
|
677
|
+
try {
|
|
678
|
+
val repo = openRepo(gitRootDir)
|
|
679
|
+
try {
|
|
680
|
+
val objectId = repo.resolve(ref)
|
|
681
|
+
if (objectId != null) {
|
|
682
|
+
result.put("ok", true)
|
|
683
|
+
result.put("oid", objectId.name)
|
|
684
|
+
} else {
|
|
685
|
+
result.put("ok", false)
|
|
686
|
+
result.put("error", "Cannot resolve ref: $ref")
|
|
687
|
+
}
|
|
688
|
+
} finally {
|
|
689
|
+
repo.close()
|
|
690
|
+
}
|
|
691
|
+
} catch (e: Exception) {
|
|
692
|
+
result.put("ok", false)
|
|
693
|
+
result.put("error", e.message ?: "Unknown error")
|
|
694
|
+
}
|
|
695
|
+
return result.toString()
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// ─── Git current branch (JGit) ──────────────────────────────────
|
|
699
|
+
|
|
700
|
+
/**
|
|
701
|
+
* Get the current branch name.
|
|
702
|
+
* Returns the branch name or "HEAD" if detached.
|
|
703
|
+
*/
|
|
704
|
+
fun gitCurrentBranch(gitRootDir: String): String {
|
|
705
|
+
val result = JSONObject()
|
|
706
|
+
try {
|
|
707
|
+
val repo = openRepo(gitRootDir)
|
|
708
|
+
try {
|
|
709
|
+
val branch = repo.branch
|
|
710
|
+
val fullBranch = repo.fullBranch
|
|
711
|
+
val isDetached = fullBranch == null || !fullBranch.startsWith("refs/heads/")
|
|
712
|
+
|
|
713
|
+
result.put("ok", true)
|
|
714
|
+
result.put("branch", branch ?: "")
|
|
715
|
+
result.put("isDetached", isDetached)
|
|
716
|
+
|
|
717
|
+
// Also list all branches
|
|
718
|
+
val git = Git(repo)
|
|
719
|
+
val localBranches = JSONArray()
|
|
720
|
+
for (ref in git.branchList().call()) {
|
|
721
|
+
localBranches.put(ref.name.removePrefix("refs/heads/"))
|
|
722
|
+
}
|
|
723
|
+
result.put("localBranches", localBranches)
|
|
724
|
+
|
|
725
|
+
val remoteBranches = JSONArray()
|
|
726
|
+
for (ref in git.branchList().setListMode(org.eclipse.jgit.api.ListBranchCommand.ListMode.REMOTE).call()) {
|
|
727
|
+
remoteBranches.put(ref.name.removePrefix("refs/remotes/"))
|
|
728
|
+
}
|
|
729
|
+
result.put("remoteBranches", remoteBranches)
|
|
730
|
+
} finally {
|
|
731
|
+
repo.close()
|
|
732
|
+
}
|
|
733
|
+
} catch (e: Exception) {
|
|
734
|
+
result.put("ok", false)
|
|
735
|
+
result.put("error", e.message ?: "Unknown error")
|
|
736
|
+
}
|
|
737
|
+
return result.toString()
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
// ─── Git init (JGit) ────────────────────────────────────────────
|
|
741
|
+
|
|
742
|
+
/**
|
|
743
|
+
* Initialize a new git repository.
|
|
744
|
+
*/
|
|
745
|
+
fun gitInit(
|
|
746
|
+
directory: String,
|
|
747
|
+
defaultBranch: String
|
|
748
|
+
): String {
|
|
749
|
+
val result = JSONObject()
|
|
750
|
+
try {
|
|
751
|
+
val targetDir = File(directory)
|
|
752
|
+
targetDir.mkdirs()
|
|
753
|
+
val git = Git.init()
|
|
754
|
+
.setDirectory(targetDir)
|
|
755
|
+
.setInitialBranch(defaultBranch)
|
|
756
|
+
.call()
|
|
757
|
+
|
|
758
|
+
result.put("ok", true)
|
|
759
|
+
android.util.Log.i("GitInit", "Initialized repo at $directory with branch $defaultBranch")
|
|
760
|
+
git.repository.close()
|
|
761
|
+
} catch (e: Exception) {
|
|
762
|
+
result.put("ok", false)
|
|
763
|
+
result.put("error", e.message ?: "Unknown init error")
|
|
764
|
+
}
|
|
765
|
+
return result.toString()
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
// ─── Git config (JGit) ──────────────────────────────────────────
|
|
769
|
+
|
|
770
|
+
/**
|
|
771
|
+
* Set a git config value. Primarily for remote.origin.url.
|
|
772
|
+
*/
|
|
773
|
+
fun gitSetConfig(
|
|
774
|
+
gitRootDir: String,
|
|
775
|
+
section: String,
|
|
776
|
+
subsection: String?,
|
|
777
|
+
name: String,
|
|
778
|
+
value: String
|
|
779
|
+
): String {
|
|
780
|
+
val result = JSONObject()
|
|
781
|
+
try {
|
|
782
|
+
val repo = openRepo(gitRootDir)
|
|
783
|
+
try {
|
|
784
|
+
val config = repo.config
|
|
785
|
+
config.setString(section, subsection, name, value)
|
|
786
|
+
config.save()
|
|
787
|
+
result.put("ok", true)
|
|
788
|
+
} finally {
|
|
789
|
+
repo.close()
|
|
790
|
+
}
|
|
791
|
+
} catch (e: Exception) {
|
|
792
|
+
result.put("ok", false)
|
|
793
|
+
result.put("error", e.message ?: "Unknown config error")
|
|
794
|
+
}
|
|
795
|
+
return result.toString()
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
/**
|
|
799
|
+
* Add a remote to the repository.
|
|
800
|
+
*/
|
|
801
|
+
fun gitAddRemote(
|
|
802
|
+
gitRootDir: String,
|
|
803
|
+
remoteName: String,
|
|
804
|
+
url: String
|
|
805
|
+
): String {
|
|
806
|
+
val result = JSONObject()
|
|
807
|
+
try {
|
|
808
|
+
val repo = openRepo(gitRootDir)
|
|
809
|
+
try {
|
|
810
|
+
val config = repo.config
|
|
811
|
+
config.setString("remote", remoteName, "url", url)
|
|
812
|
+
config.setString("remote", remoteName, "fetch", "+refs/heads/*:refs/remotes/$remoteName/*")
|
|
813
|
+
config.save()
|
|
814
|
+
result.put("ok", true)
|
|
815
|
+
} finally {
|
|
816
|
+
repo.close()
|
|
817
|
+
}
|
|
818
|
+
} catch (e: Exception) {
|
|
819
|
+
result.put("ok", false)
|
|
820
|
+
result.put("error", e.message ?: "Unknown error")
|
|
821
|
+
}
|
|
822
|
+
return result.toString()
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
// ─── Git read blob (JGit) ───────────────────────────────────────
|
|
826
|
+
|
|
827
|
+
/**
|
|
828
|
+
* Read a file (blob) at a specific commit reference.
|
|
829
|
+
* Returns base64-encoded content for binary, or utf8 text.
|
|
830
|
+
*/
|
|
831
|
+
fun gitReadBlob(
|
|
832
|
+
gitRootDir: String,
|
|
833
|
+
ref: String,
|
|
834
|
+
filepath: String,
|
|
835
|
+
asBase64: Boolean
|
|
836
|
+
): String {
|
|
837
|
+
val result = JSONObject()
|
|
838
|
+
try {
|
|
839
|
+
val repo = openRepo(gitRootDir)
|
|
840
|
+
try {
|
|
841
|
+
val commitId = repo.resolve(ref)
|
|
842
|
+
?: throw Exception("Cannot resolve ref: $ref")
|
|
843
|
+
val walk = RevWalk(repo)
|
|
844
|
+
val commit = walk.parseCommit(commitId)
|
|
845
|
+
val tree = commit.tree
|
|
846
|
+
|
|
847
|
+
val treeWalk = TreeWalk.forPath(repo, filepath, tree)
|
|
848
|
+
if (treeWalk == null) {
|
|
849
|
+
result.put("ok", false)
|
|
850
|
+
result.put("error", "File not found in tree: $filepath at $ref")
|
|
851
|
+
walk.close()
|
|
852
|
+
return result.toString()
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
val objectId = treeWalk.getObjectId(0)
|
|
856
|
+
val loader = repo.newObjectReader().open(objectId)
|
|
857
|
+
val bytes = loader.bytes
|
|
858
|
+
|
|
859
|
+
result.put("ok", true)
|
|
860
|
+
if (asBase64) {
|
|
861
|
+
result.put("content", Base64.encodeToString(bytes, Base64.NO_WRAP))
|
|
862
|
+
result.put("encoding", "base64")
|
|
863
|
+
} else {
|
|
864
|
+
result.put("content", String(bytes, Charsets.UTF_8))
|
|
865
|
+
result.put("encoding", "utf8")
|
|
866
|
+
}
|
|
867
|
+
result.put("size", bytes.size)
|
|
868
|
+
|
|
869
|
+
walk.close()
|
|
870
|
+
} finally {
|
|
871
|
+
repo.close()
|
|
872
|
+
}
|
|
873
|
+
} catch (e: Exception) {
|
|
874
|
+
result.put("ok", false)
|
|
875
|
+
result.put("error", e.message ?: "Unknown error")
|
|
876
|
+
}
|
|
877
|
+
return result.toString()
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
// ─── Git diff trees (JGit) ──────────────────────────────────────
|
|
881
|
+
|
|
882
|
+
/**
|
|
883
|
+
* Diff two commits and return the list of changed files.
|
|
884
|
+
* This is the native equivalent of diffCommitTrees in JS.
|
|
885
|
+
*/
|
|
886
|
+
fun gitDiffTrees(
|
|
887
|
+
gitRootDir: String,
|
|
888
|
+
oldRef: String,
|
|
889
|
+
newRef: String
|
|
890
|
+
): String {
|
|
891
|
+
val result = JSONObject()
|
|
892
|
+
try {
|
|
893
|
+
val repo = openRepo(gitRootDir)
|
|
894
|
+
try {
|
|
895
|
+
val walk = RevWalk(repo)
|
|
896
|
+
val oldCommit = walk.parseCommit(repo.resolve(oldRef)
|
|
897
|
+
?: throw Exception("Cannot resolve ref: $oldRef"))
|
|
898
|
+
val newCommit = walk.parseCommit(repo.resolve(newRef)
|
|
899
|
+
?: throw Exception("Cannot resolve ref: $newRef"))
|
|
900
|
+
|
|
901
|
+
val diffFormatter = DiffFormatter(ByteArrayOutputStream())
|
|
902
|
+
diffFormatter.setRepository(repo)
|
|
903
|
+
val diffs = diffFormatter.scan(oldCommit.tree, newCommit.tree)
|
|
904
|
+
|
|
905
|
+
val files = JSONArray()
|
|
906
|
+
for (diff in diffs) {
|
|
907
|
+
val obj = JSONObject()
|
|
908
|
+
when (diff.changeType) {
|
|
909
|
+
DiffEntry.ChangeType.ADD -> {
|
|
910
|
+
obj.put("path", diff.newPath)
|
|
911
|
+
obj.put("type", "add")
|
|
912
|
+
}
|
|
913
|
+
DiffEntry.ChangeType.DELETE -> {
|
|
914
|
+
obj.put("path", diff.oldPath)
|
|
915
|
+
obj.put("type", "delete")
|
|
916
|
+
}
|
|
917
|
+
DiffEntry.ChangeType.MODIFY -> {
|
|
918
|
+
obj.put("path", diff.newPath)
|
|
919
|
+
obj.put("type", "modify")
|
|
920
|
+
}
|
|
921
|
+
DiffEntry.ChangeType.RENAME -> {
|
|
922
|
+
obj.put("path", diff.newPath)
|
|
923
|
+
obj.put("oldPath", diff.oldPath)
|
|
924
|
+
obj.put("type", "modify")
|
|
925
|
+
}
|
|
926
|
+
DiffEntry.ChangeType.COPY -> {
|
|
927
|
+
obj.put("path", diff.newPath)
|
|
928
|
+
obj.put("type", "add")
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
files.put(obj)
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
diffFormatter.close()
|
|
935
|
+
walk.close()
|
|
936
|
+
|
|
937
|
+
result.put("ok", true)
|
|
938
|
+
result.put("files", files)
|
|
939
|
+
} finally {
|
|
940
|
+
repo.close()
|
|
941
|
+
}
|
|
942
|
+
} catch (e: Exception) {
|
|
943
|
+
result.put("ok", false)
|
|
944
|
+
result.put("error", e.message ?: "Unknown error")
|
|
945
|
+
}
|
|
946
|
+
return result.toString()
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
// ─── Git discard file changes (JGit) ────────────────────────────
|
|
950
|
+
|
|
951
|
+
/**
|
|
952
|
+
* Discard changes to a specific file by checking out the HEAD version.
|
|
953
|
+
* If the file doesn't exist in HEAD (new file), delete it from working tree.
|
|
954
|
+
*/
|
|
955
|
+
fun gitDiscardFileChanges(
|
|
956
|
+
gitRootDir: String,
|
|
957
|
+
filepath: String
|
|
958
|
+
): String {
|
|
959
|
+
val result = JSONObject()
|
|
960
|
+
try {
|
|
961
|
+
val repo = openRepo(gitRootDir)
|
|
962
|
+
try {
|
|
963
|
+
val git = Git(repo)
|
|
964
|
+
val headId = repo.resolve(Constants.HEAD)
|
|
965
|
+
|
|
966
|
+
if (headId != null) {
|
|
967
|
+
// Check if file exists in HEAD
|
|
968
|
+
val walk = RevWalk(repo)
|
|
969
|
+
val commit = walk.parseCommit(headId)
|
|
970
|
+
val treeWalk = TreeWalk.forPath(repo, filepath, commit.tree)
|
|
971
|
+
walk.close()
|
|
972
|
+
|
|
973
|
+
if (treeWalk != null) {
|
|
974
|
+
// File exists in HEAD — checkout it
|
|
975
|
+
git.checkout()
|
|
976
|
+
.addPath(filepath)
|
|
977
|
+
.call()
|
|
978
|
+
result.put("ok", true)
|
|
979
|
+
result.put("action", "checkout")
|
|
980
|
+
} else {
|
|
981
|
+
// File doesn't exist in HEAD — it's untracked, delete it
|
|
982
|
+
val targetFile = File(File(gitRootDir), filepath)
|
|
983
|
+
if (targetFile.exists()) {
|
|
984
|
+
targetFile.delete()
|
|
985
|
+
}
|
|
986
|
+
result.put("ok", true)
|
|
987
|
+
result.put("action", "delete")
|
|
988
|
+
}
|
|
989
|
+
} else {
|
|
990
|
+
result.put("ok", false)
|
|
991
|
+
result.put("error", "Cannot resolve HEAD")
|
|
992
|
+
}
|
|
993
|
+
} finally {
|
|
994
|
+
repo.close()
|
|
995
|
+
}
|
|
996
|
+
} catch (e: Exception) {
|
|
997
|
+
result.put("ok", false)
|
|
998
|
+
result.put("error", e.message ?: "Unknown error")
|
|
999
|
+
}
|
|
1000
|
+
return result.toString()
|
|
1001
|
+
}
|
|
551
1002
|
}
|
package/build/index.d.ts
CHANGED
|
@@ -203,6 +203,100 @@ interface IExternalStorageModule {
|
|
|
203
203
|
* @returns JSON string: `{"ok":true,"ref":"abc123"}` or `{"ok":false,"error":"..."}`
|
|
204
204
|
*/
|
|
205
205
|
gitReset(gitRootDir: string, ref: string, mode: string): Promise<string>;
|
|
206
|
+
/**
|
|
207
|
+
* Clone a remote repository using native JGit.
|
|
208
|
+
*
|
|
209
|
+
* @param url Remote repository URL
|
|
210
|
+
* @param directory Destination directory
|
|
211
|
+
* @param branch Branch to clone (null for default)
|
|
212
|
+
* @param depth Depth for shallow clone (0 for full)
|
|
213
|
+
* @param singleBranch Whether to clone only the specified branch
|
|
214
|
+
* @param noTags Whether to skip fetching tags
|
|
215
|
+
* @param headers Optional HTTP headers as JSON string
|
|
216
|
+
* @returns JSON string: `{"ok":true,"head":"abc123"}` or `{"ok":false,"error":"..."}`
|
|
217
|
+
*/
|
|
218
|
+
gitClone(url: string, directory: string, branch: string | null, depth: number, singleBranch: boolean, noTags: boolean, headers?: string | null): Promise<string>;
|
|
219
|
+
/**
|
|
220
|
+
* Get commit history using native git log.
|
|
221
|
+
*
|
|
222
|
+
* @param gitRootDir Absolute path to the git working directory
|
|
223
|
+
* @param ref Branch or ref to start from (null for all)
|
|
224
|
+
* @param maxCount Maximum number of commits to return
|
|
225
|
+
* @returns JSON string: `{"ok":true,"commits":[{"oid","message","authorName","authorEmail","timestamp","parentOids"}]}`
|
|
226
|
+
*/
|
|
227
|
+
gitLog(gitRootDir: string, ref: string | null, maxCount: number): Promise<string>;
|
|
228
|
+
/**
|
|
229
|
+
* Resolve a git reference to its SHA-1 hash.
|
|
230
|
+
*
|
|
231
|
+
* @param gitRootDir Absolute path to the git working directory
|
|
232
|
+
* @param ref Reference to resolve (e.g. "HEAD", "refs/heads/main", "origin/main")
|
|
233
|
+
* @returns JSON string: `{"ok":true,"oid":"abc123"}` or `{"ok":false,"error":"..."}`
|
|
234
|
+
*/
|
|
235
|
+
gitResolveRef(gitRootDir: string, ref: string): Promise<string>;
|
|
236
|
+
/**
|
|
237
|
+
* Get current branch name and branch listings.
|
|
238
|
+
*
|
|
239
|
+
* @param gitRootDir Absolute path to the git working directory
|
|
240
|
+
* @returns JSON string: `{"ok":true,"branch":"main","isDetached":false,"localBranches":[],"remoteBranches":[]}`
|
|
241
|
+
*/
|
|
242
|
+
gitCurrentBranch(gitRootDir: string): Promise<string>;
|
|
243
|
+
/**
|
|
244
|
+
* Initialize a new git repository.
|
|
245
|
+
*
|
|
246
|
+
* @param directory Directory to initialize
|
|
247
|
+
* @param defaultBranch Default branch name (e.g. "main")
|
|
248
|
+
* @returns JSON string: `{"ok":true}` or `{"ok":false,"error":"..."}`
|
|
249
|
+
*/
|
|
250
|
+
gitInit(directory: string, defaultBranch: string): Promise<string>;
|
|
251
|
+
/**
|
|
252
|
+
* Set a git config value.
|
|
253
|
+
*
|
|
254
|
+
* @param gitRootDir Absolute path to the git working directory
|
|
255
|
+
* @param section Config section (e.g. "remote")
|
|
256
|
+
* @param subsection Config subsection (e.g. "origin"), null for no subsection
|
|
257
|
+
* @param name Config key (e.g. "url")
|
|
258
|
+
* @param value Config value
|
|
259
|
+
* @returns JSON string: `{"ok":true}` or `{"ok":false,"error":"..."}`
|
|
260
|
+
*/
|
|
261
|
+
gitSetConfig(gitRootDir: string, section: string, subsection: string | null, name: string, value: string): Promise<string>;
|
|
262
|
+
/**
|
|
263
|
+
* Add a remote to the repository.
|
|
264
|
+
*
|
|
265
|
+
* @param gitRootDir Absolute path to the git working directory
|
|
266
|
+
* @param remoteName Remote name (e.g. "origin")
|
|
267
|
+
* @param url Remote URL
|
|
268
|
+
* @returns JSON string: `{"ok":true}` or `{"ok":false,"error":"..."}`
|
|
269
|
+
*/
|
|
270
|
+
gitAddRemote(gitRootDir: string, remoteName: string, url: string): Promise<string>;
|
|
271
|
+
/**
|
|
272
|
+
* Read a file (blob) at a specific commit reference.
|
|
273
|
+
*
|
|
274
|
+
* @param gitRootDir Absolute path to the git working directory
|
|
275
|
+
* @param ref Commit ref to read from
|
|
276
|
+
* @param filepath Relative path within the repository
|
|
277
|
+
* @param asBase64 If true, return content as base64; otherwise as utf8 string
|
|
278
|
+
* @returns JSON string: `{"ok":true,"content":"...","encoding":"base64"|"utf8","size":N}`
|
|
279
|
+
*/
|
|
280
|
+
gitReadBlob(gitRootDir: string, ref: string, filepath: string, asBase64: boolean): Promise<string>;
|
|
281
|
+
/**
|
|
282
|
+
* Diff two commits and return changed files list.
|
|
283
|
+
* Native equivalent of tree-walking diff.
|
|
284
|
+
*
|
|
285
|
+
* @param gitRootDir Absolute path to the git working directory
|
|
286
|
+
* @param oldRef Old commit ref
|
|
287
|
+
* @param newRef New commit ref
|
|
288
|
+
* @returns JSON string: `{"ok":true,"files":[{"path":"...","type":"add"|"modify"|"delete"}]}`
|
|
289
|
+
*/
|
|
290
|
+
gitDiffTrees(gitRootDir: string, oldRef: string, newRef: string): Promise<string>;
|
|
291
|
+
/**
|
|
292
|
+
* Discard uncommitted changes for a specific file.
|
|
293
|
+
* Checks out the HEAD version, or deletes the file if it's untracked.
|
|
294
|
+
*
|
|
295
|
+
* @param gitRootDir Absolute path to the git working directory
|
|
296
|
+
* @param filepath Relative path of the file to discard
|
|
297
|
+
* @returns JSON string: `{"ok":true,"action":"checkout"|"delete"}` or `{"ok":false,"error":"..."}`
|
|
298
|
+
*/
|
|
299
|
+
gitDiscardFileChanges(gitRootDir: string, filepath: string): Promise<string>;
|
|
206
300
|
}
|
|
207
301
|
export declare const ExternalStorage: IExternalStorageModule;
|
|
208
302
|
/**
|
package/build/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AA8BA,UAAU,QAAQ;IAChB,MAAM,EAAE,OAAO,CAAC;IAChB,WAAW,EAAE,OAAO,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,+BAA+B;IAC/B,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,UAAU,gBAAgB;IACxB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,UAAU,oBAAoB;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,UAAU,2BAA2B;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,oDAAoD;IACpD,UAAU,EAAE,MAAM,CAAC;IACnB,kEAAkE;IAClE,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,UAAU,gBAAgB;IACxB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,UAAU,mBAAmB;IAC3B,gCAAgC;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,UAAU,sBAAsB;IAC9B,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACvC,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IAEzC,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACzC,8FAA8F;IAC9F,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAClD,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEnC,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5C,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC9C,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5D,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpE;;;;;;;;;OASG;IACH,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,aAAa,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7F,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,cAAc,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;IACvF,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAExC,yBAAyB,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9C,2BAA2B,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/C,8FAA8F;IAC9F,wBAAwB,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IAE7C;;;;;;;;;;;OAWG;IACH,cAAc,CACZ,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC/B,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAEjC;;;;;OAKG;IACH,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAE1F;;;;;;;;;;;OAWG;IACH,qBAAqB,CACnB,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC/B,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,2BAA2B,CAAC,CAAC;IAExC;;;;;;;;OAQG;IACH,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAExE;;;;;;;;;;;;;;;;;OAiBG;IACH,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,aAAa,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAEjF;;;;;;;;;;;;OAYG;IACH,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAE/C;;;OAGG;IACH,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAEpD;;;;;;;;;OASG;IACH,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAEnD;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAErJ;;;;;;;;OAQG;IACH,QAAQ,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAE3G;;;;;;;;OAQG;IACH,uBAAuB,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAE7F;;;;;;;;;OASG;IACH,eAAe,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAE/G;;;;;;;;OAQG;IACH,QAAQ,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/build/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAExC,IAAI,OAA2C,CAAC;AAEhD;;;;GAIG;AACH,SAAS,eAAe;IACtB,IAAI,OAAO;QAAE,OAAO,OAAO,CAAC;IAC5B,IAAI,QAAQ,CAAC,EAAE,KAAK,SAAS,IAAI,QAAQ,CAAC,EAAE,KAAK,KAAK,EAAE,CAAC;QACvD,MAAM,IAAI,KAAK,CAAC,oEAAoE,CAAC,CAAC;IACxF,CAAC;IACD,iEAAiE;IACjE,MAAM,EAAE,mBAAmB,EAAE,GAAG,OAAO,CAAC,mBAAmB,CAAsE,CAAC;IAClI,OAAO,GAAG,mBAAmB,CAAC,iBAAiB,CAAC,CAAC;IACjD,OAAO,OAAO,CAAC;AACjB,CAAC;AAiPD,MAAM,CAAC,MAAM,eAAe,GAA2B,IAAI,KAAK,CAAC,EAA4B,EAAE;IAC7F,GAAG,CAAC,OAAO,EAAE,QAAQ;QACnB,MAAM,GAAG,GAAG,eAAe,EAAE,CAAC;QAC9B,OAAQ,GAAmD,CAAC,QAAQ,CAAC,CAAC;IACxE,CAAC;CACF,CAAC,CAAC;AAEH;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,SAAiB;IAC3C,IAAI,SAAS,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QACpC,OAAO,SAAS,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IAC3C,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC","sourcesContent":["/**\n * TypeScript bindings for the ExternalStorage native module.\n *\n * This module uses raw java.io.File on Android to bypass Expo FileSystem's\n * directory whitelist. It allows reading/writing to shared external storage\n * when MANAGE_EXTERNAL_STORAGE permission is granted.\n *\n * All path arguments are plain filesystem paths (e.g. \"/storage/emulated/0/Documents/TidGi/\").\n * Do NOT pass file:// URIs — strip the scheme before calling.\n */\nimport { Platform } from 'react-native';\n\nlet _module: IExternalStorageModule | undefined;\n\n/**\n * Lazily load the native module. Wrapped in a function so that the app does NOT\n * crash at import time if the native module is missing (e.g. on iOS or when the\n * binary was built without it).\n */\nfunction getNativeModule(): IExternalStorageModule {\n if (_module) return _module;\n if (Platform.OS !== 'android' && Platform.OS !== 'ios') {\n throw new Error('ExternalStorage native module is only available on Android and iOS');\n }\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const { requireNativeModule } = require('expo-modules-core') as { requireNativeModule: (name: string) => IExternalStorageModule };\n _module = requireNativeModule('ExternalStorage');\n return _module;\n}\n\ninterface FileInfo {\n exists: boolean;\n isDirectory: boolean;\n size: number;\n /** Milliseconds since epoch */\n modificationTime: number;\n}\n\ninterface BatchWriteResult {\n writtenCount: number;\n}\n\ninterface HttpPostToFileResult {\n statusCode: number;\n headers: Record<string, string>;\n bytesWritten: number;\n}\n\ninterface DownloadFileResumableResult {\n statusCode: number;\n /** Final size of the file on disk after download */\n totalBytes: number;\n /** true if the download resumed from a partial file (HTTP 206) */\n resumed: boolean;\n}\n\ninterface ExtractTarResult {\n filesExtracted: number;\n}\n\ninterface ReadFileChunkResult {\n /** Base64-encoded chunk data */\n data: string;\n bytesRead: number;\n}\n\ninterface IExternalStorageModule {\n exists(path: string): Promise<boolean>;\n getInfo(path: string): Promise<FileInfo>;\n\n mkdir(path: string): Promise<void>;\n readDir(path: string): Promise<string[]>;\n /** Recursively list all files under a directory, returning relative paths. Skips .git etc. */\n readDirRecursive(path: string): Promise<string[]>;\n rmdir(path: string): Promise<void>;\n\n readFileUtf8(path: string): Promise<string>;\n readFileBase64(path: string): Promise<string>;\n writeFileUtf8(path: string, content: string): Promise<void>;\n writeFileBase64(path: string, base64Content: string): Promise<void>;\n /**\n * Append a Base64-encoded chunk to a file, optionally truncating first.\n *\n * Designed for streaming large writes from JS in bounded-memory chunks\n * (e.g. 512 KB each) so the JVM never allocates the full file content,\n * avoiding OOM on 50+ MB git pack files.\n *\n * @param truncateFirst Pass `true` for the first chunk to create/truncate\n * the file, then `false` for subsequent chunks.\n */\n appendFileBase64(path: string, base64Content: string, truncateFirst: boolean): Promise<void>;\n writeFilesBase64(paths: string[], base64Contents: string[]): Promise<BatchWriteResult>;\n deleteFile(path: string): Promise<void>;\n\n isExternalStorageWritable(): Promise<boolean>;\n getExternalStorageDirectory(): Promise<string>;\n /** Android 11+ (API 30): check if MANAGE_EXTERNAL_STORAGE is granted. Pre-30 returns true. */\n isExternalStorageManager(): Promise<boolean>;\n\n /**\n * HTTP POST with the response body streamed directly to a file on disk,\n * **never buffering the full response in JVM/Hermes heap**.\n *\n * Designed for git-upload-pack which can return 100+ MB packfiles.\n *\n * @param url Target URL\n * @param headers HTTP headers as `{ key: value }`\n * @param bodyBase64 Request body encoded as Base64 (binary git protocol data)\n * @param destPath Plain filesystem path to write the response body to\n * @param contentType MIME type for the request body\n */\n httpPostToFile(\n url: string,\n headers: Record<string, string>,\n bodyBase64: string,\n destPath: string,\n contentType: string,\n ): Promise<HttpPostToFileResult>;\n\n /**\n * Read a chunk of a file starting at `offset` for up to `length` bytes.\n * Returns Base64-encoded data and actual bytes read.\n *\n * Use this to stream a large file into JS in bounded-memory chunks.\n */\n readFileChunk(path: string, offset: number, length: number): Promise<ReadFileChunkResult>;\n\n /**\n * Download a file via HTTP GET with resumable download support.\n *\n * If `destPath` already exists on disk (from a previous interrupted download),\n * sends `Range: bytes=<existingSize>-` to resume. The server must respond\n * with 206 Partial Content for resume to work; otherwise the file is\n * overwritten from scratch (200 response).\n *\n * @param url Target URL\n * @param headers Extra HTTP headers (e.g. Authorization, ETag)\n * @param destPath Plain filesystem path for the downloaded file\n */\n downloadFileResumable(\n url: string,\n headers: Record<string, string>,\n destPath: string,\n ): Promise<DownloadFileResumableResult>;\n\n /**\n * Extract an uncompressed tar archive to a destination directory.\n * Uses a native tar parser — no third-party dependency.\n * Supports POSIX ustar and GNU long-name extensions.\n * Validates paths to prevent directory traversal attacks.\n *\n * @param tarPath Path to the .tar file\n * @param destDir Destination directory (created if needed)\n */\n extractTar(tarPath: string, destDir: string): Promise<ExtractTarResult>;\n\n /**\n * Parse a batch of TiddlyWiki tiddler files entirely in native Kotlin.\n *\n * This is the critical performance optimization for initial wiki loading:\n * a single bridge call processes 100+ files in parallel, returning a\n * ready-to-inject JSON array string. Eliminates per-file bridge round-trips.\n *\n * Supports .tid, .json, and .meta files. Applies skinny logic:\n * - System tiddlers ($:/) → always full text\n * - Plugins (application/json + plugin-type) → always full text\n * - Module tiddlers (module-type) → always full text\n * - Small tiddlers (< 10KB body) → full text\n * - Large user tiddlers → skinny (_is_skinny: \"yes\", text omitted)\n *\n * @param filePaths Array of absolute filesystem paths\n * @param quickLoadMode If true, all tiddlers returned as skinny\n * @returns JSON string: serialized array of tiddler field objects\n */\n batchParseTidFiles(filePaths: string[], quickLoadMode: boolean): Promise<string>;\n\n /**\n * Lightweight native git status using direct git-index parsing.\n *\n * Parses `.git/index` to get tracked files and their stat-cache entries,\n * then compares against the working directory using file size and mtime.\n * Orders of magnitude faster than isomorphic-git's `statusMatrix` because:\n * - No JS↔Native bridge round-trips per file\n * - Uses stat-cache (size+mtime) instead of SHA-1 re-hashing\n * - Parallel file walking in Java\n *\n * @param gitRootDir The root directory of the git repository (parent of .git/)\n * @returns JSON string: `[{\"path\":\"tiddlers/foo.tid\",\"type\":\"add\"|\"modify\"|\"delete\"}, ...]`\n */\n gitStatus(gitRootDir: string): Promise<string>;\n\n /**\n * Debug function returning diagnostic info about the git repository state.\n * @returns JSON string with root/gitDir/index existence and git dir children\n */\n gitStatusDebug(gitRootDir: string): Promise<string>;\n\n /**\n * Build `.git/index` natively by reading the HEAD tree from pack files,\n * stat'ing all files on disk, and writing a v2 index file.\n *\n * This is used after archive clone where TidGi Desktop's tar export\n * doesn't include `.git/index`.\n *\n * @param gitRootDir The root directory of the git repository (parent of .git/)\n * @returns JSON string: `{\"ok\":true,\"entries\":N,\"indexSize\":M}` or `{\"ok\":false,\"error\":\"...\"}`\n */\n buildGitIndex(gitRootDir: string): Promise<string>;\n\n /**\n * Push local branch to remote using native JGit (efficient pack building).\n * Avoids OOM from isomorphic-git's JS-based pack construction on large repos.\n *\n * @param gitRootDir Absolute path to the git working directory\n * @param remoteName Remote name (e.g. \"origin\")\n * @param localBranch Local branch name (e.g. \"main\")\n * @param remoteBranch Remote branch ref (e.g. \"refs/heads/mobile-incoming\")\n * @param force Whether to force push\n * @param headers Optional HTTP headers as JSON string\n * @returns JSON string: `{\"ok\":true,\"updates\":[...]}` or `{\"ok\":false,\"error\":\"...\"}`\n */\n gitPush(gitRootDir: string, remoteName: string, localBranch: string, remoteBranch: string, force: boolean, headers?: string | null): Promise<string>;\n\n /**\n * Fetch from remote using native JGit (efficient pack handling).\n *\n * @param gitRootDir Absolute path to the git working directory\n * @param remoteName Remote name (e.g. \"origin\")\n * @param branch Branch to fetch\n * @param headers Optional HTTP headers as JSON string\n * @returns JSON string: `{\"ok\":true,\"updates\":[...]}` or `{\"ok\":false,\"error\":\"...\"}`\n */\n gitFetch(gitRootDir: string, remoteName: string, branch: string, headers?: string | null): Promise<string>;\n\n /**\n * Compare two commits and checkout only changed/new files to the working tree.\n * Avoids full checkout which OOMs on large repos.\n *\n * @param gitRootDir Absolute path to the git working directory\n * @param oldOid Old commit SHA (before fetch)\n * @param newOid New commit SHA (after fetch)\n * @returns JSON string: `{\"ok\":true,\"count\":N,\"files\":[...]}` or `{\"ok\":false,\"error\":\"...\"}`\n */\n gitCheckoutChangedFiles(gitRootDir: string, oldOid: string, newOid: string): Promise<string>;\n\n /**\n * Stage all changes and commit using native git (JGit on Android).\n * Replaces isomorphic-git's statusMatrix + add/remove loop which OOMs on large repos.\n *\n * @param gitRootDir Absolute path to the git working directory\n * @param message Commit message\n * @param authorName Author name\n * @param authorEmail Author email\n * @returns JSON string: `{\"ok\":true,\"commitId\":\"abc123\"}` or `{\"ok\":false,\"error\":\"...\"}`\n */\n gitAddAndCommit(gitRootDir: string, message: string, authorName: string, authorEmail: string): Promise<string>;\n\n /**\n * Reset the current branch to a specific ref using native git (JGit on Android).\n * Supports hard, mixed, and soft reset modes.\n *\n * @param gitRootDir Absolute path to the git working directory\n * @param ref Target ref (e.g. \"origin/main\", commit SHA)\n * @param mode Reset mode: \"hard\", \"mixed\", or \"soft\"\n * @returns JSON string: `{\"ok\":true,\"ref\":\"abc123\"}` or `{\"ok\":false,\"error\":\"...\"}`\n */\n gitReset(gitRootDir: string, ref: string, mode: string): Promise<string>;\n}\n\nexport const ExternalStorage: IExternalStorageModule = new Proxy({} as IExternalStorageModule, {\n get(_target, property) {\n const mod = getNativeModule();\n return (mod as unknown as Record<string | symbol, unknown>)[property];\n },\n});\n\n/**\n * Strip file:// prefix from a URI to produce a plain filesystem path.\n * Safe to call on paths that are already plain.\n */\nexport function toPlainPath(uriOrPath: string): string {\n if (uriOrPath.startsWith('file://')) {\n return uriOrPath.slice('file://'.length);\n }\n return uriOrPath;\n}\n\nexport type { BatchWriteResult, DownloadFileResumableResult, ExtractTarResult, FileInfo, HttpPostToFileResult, IExternalStorageModule, ReadFileChunkResult };\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAExC,IAAI,OAA2C,CAAC;AAEhD;;;;GAIG;AACH,SAAS,eAAe;IACtB,IAAI,OAAO;QAAE,OAAO,OAAO,CAAC;IAC5B,IAAI,QAAQ,CAAC,EAAE,KAAK,SAAS,IAAI,QAAQ,CAAC,EAAE,KAAK,KAAK,EAAE,CAAC;QACvD,MAAM,IAAI,KAAK,CAAC,oEAAoE,CAAC,CAAC;IACxF,CAAC;IACD,iEAAiE;IACjE,MAAM,EAAE,mBAAmB,EAAE,GAAG,OAAO,CAAC,mBAAmB,CAAsE,CAAC;IAClI,OAAO,GAAG,mBAAmB,CAAC,iBAAiB,CAAC,CAAC;IACjD,OAAO,OAAO,CAAC;AACjB,CAAC;AAyVD,MAAM,CAAC,MAAM,eAAe,GAA2B,IAAI,KAAK,CAAC,EAA4B,EAAE;IAC7F,GAAG,CAAC,OAAO,EAAE,QAAQ;QACnB,MAAM,GAAG,GAAG,eAAe,EAAE,CAAC;QAC9B,OAAQ,GAAmD,CAAC,QAAQ,CAAC,CAAC;IACxE,CAAC;CACF,CAAC,CAAC;AAEH;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,SAAiB;IAC3C,IAAI,SAAS,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QACpC,OAAO,SAAS,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IAC3C,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC","sourcesContent":["/**\n * TypeScript bindings for the ExternalStorage native module.\n *\n * This module uses raw java.io.File on Android to bypass Expo FileSystem's\n * directory whitelist. It allows reading/writing to shared external storage\n * when MANAGE_EXTERNAL_STORAGE permission is granted.\n *\n * All path arguments are plain filesystem paths (e.g. \"/storage/emulated/0/Documents/TidGi/\").\n * Do NOT pass file:// URIs — strip the scheme before calling.\n */\nimport { Platform } from 'react-native';\n\nlet _module: IExternalStorageModule | undefined;\n\n/**\n * Lazily load the native module. Wrapped in a function so that the app does NOT\n * crash at import time if the native module is missing (e.g. on iOS or when the\n * binary was built without it).\n */\nfunction getNativeModule(): IExternalStorageModule {\n if (_module) return _module;\n if (Platform.OS !== 'android' && Platform.OS !== 'ios') {\n throw new Error('ExternalStorage native module is only available on Android and iOS');\n }\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const { requireNativeModule } = require('expo-modules-core') as { requireNativeModule: (name: string) => IExternalStorageModule };\n _module = requireNativeModule('ExternalStorage');\n return _module;\n}\n\ninterface FileInfo {\n exists: boolean;\n isDirectory: boolean;\n size: number;\n /** Milliseconds since epoch */\n modificationTime: number;\n}\n\ninterface BatchWriteResult {\n writtenCount: number;\n}\n\ninterface HttpPostToFileResult {\n statusCode: number;\n headers: Record<string, string>;\n bytesWritten: number;\n}\n\ninterface DownloadFileResumableResult {\n statusCode: number;\n /** Final size of the file on disk after download */\n totalBytes: number;\n /** true if the download resumed from a partial file (HTTP 206) */\n resumed: boolean;\n}\n\ninterface ExtractTarResult {\n filesExtracted: number;\n}\n\ninterface ReadFileChunkResult {\n /** Base64-encoded chunk data */\n data: string;\n bytesRead: number;\n}\n\ninterface IExternalStorageModule {\n exists(path: string): Promise<boolean>;\n getInfo(path: string): Promise<FileInfo>;\n\n mkdir(path: string): Promise<void>;\n readDir(path: string): Promise<string[]>;\n /** Recursively list all files under a directory, returning relative paths. Skips .git etc. */\n readDirRecursive(path: string): Promise<string[]>;\n rmdir(path: string): Promise<void>;\n\n readFileUtf8(path: string): Promise<string>;\n readFileBase64(path: string): Promise<string>;\n writeFileUtf8(path: string, content: string): Promise<void>;\n writeFileBase64(path: string, base64Content: string): Promise<void>;\n /**\n * Append a Base64-encoded chunk to a file, optionally truncating first.\n *\n * Designed for streaming large writes from JS in bounded-memory chunks\n * (e.g. 512 KB each) so the JVM never allocates the full file content,\n * avoiding OOM on 50+ MB git pack files.\n *\n * @param truncateFirst Pass `true` for the first chunk to create/truncate\n * the file, then `false` for subsequent chunks.\n */\n appendFileBase64(path: string, base64Content: string, truncateFirst: boolean): Promise<void>;\n writeFilesBase64(paths: string[], base64Contents: string[]): Promise<BatchWriteResult>;\n deleteFile(path: string): Promise<void>;\n\n isExternalStorageWritable(): Promise<boolean>;\n getExternalStorageDirectory(): Promise<string>;\n /** Android 11+ (API 30): check if MANAGE_EXTERNAL_STORAGE is granted. Pre-30 returns true. */\n isExternalStorageManager(): Promise<boolean>;\n\n /**\n * HTTP POST with the response body streamed directly to a file on disk,\n * **never buffering the full response in JVM/Hermes heap**.\n *\n * Designed for git-upload-pack which can return 100+ MB packfiles.\n *\n * @param url Target URL\n * @param headers HTTP headers as `{ key: value }`\n * @param bodyBase64 Request body encoded as Base64 (binary git protocol data)\n * @param destPath Plain filesystem path to write the response body to\n * @param contentType MIME type for the request body\n */\n httpPostToFile(\n url: string,\n headers: Record<string, string>,\n bodyBase64: string,\n destPath: string,\n contentType: string,\n ): Promise<HttpPostToFileResult>;\n\n /**\n * Read a chunk of a file starting at `offset` for up to `length` bytes.\n * Returns Base64-encoded data and actual bytes read.\n *\n * Use this to stream a large file into JS in bounded-memory chunks.\n */\n readFileChunk(path: string, offset: number, length: number): Promise<ReadFileChunkResult>;\n\n /**\n * Download a file via HTTP GET with resumable download support.\n *\n * If `destPath` already exists on disk (from a previous interrupted download),\n * sends `Range: bytes=<existingSize>-` to resume. The server must respond\n * with 206 Partial Content for resume to work; otherwise the file is\n * overwritten from scratch (200 response).\n *\n * @param url Target URL\n * @param headers Extra HTTP headers (e.g. Authorization, ETag)\n * @param destPath Plain filesystem path for the downloaded file\n */\n downloadFileResumable(\n url: string,\n headers: Record<string, string>,\n destPath: string,\n ): Promise<DownloadFileResumableResult>;\n\n /**\n * Extract an uncompressed tar archive to a destination directory.\n * Uses a native tar parser — no third-party dependency.\n * Supports POSIX ustar and GNU long-name extensions.\n * Validates paths to prevent directory traversal attacks.\n *\n * @param tarPath Path to the .tar file\n * @param destDir Destination directory (created if needed)\n */\n extractTar(tarPath: string, destDir: string): Promise<ExtractTarResult>;\n\n /**\n * Parse a batch of TiddlyWiki tiddler files entirely in native Kotlin.\n *\n * This is the critical performance optimization for initial wiki loading:\n * a single bridge call processes 100+ files in parallel, returning a\n * ready-to-inject JSON array string. Eliminates per-file bridge round-trips.\n *\n * Supports .tid, .json, and .meta files. Applies skinny logic:\n * - System tiddlers ($:/) → always full text\n * - Plugins (application/json + plugin-type) → always full text\n * - Module tiddlers (module-type) → always full text\n * - Small tiddlers (< 10KB body) → full text\n * - Large user tiddlers → skinny (_is_skinny: \"yes\", text omitted)\n *\n * @param filePaths Array of absolute filesystem paths\n * @param quickLoadMode If true, all tiddlers returned as skinny\n * @returns JSON string: serialized array of tiddler field objects\n */\n batchParseTidFiles(filePaths: string[], quickLoadMode: boolean): Promise<string>;\n\n /**\n * Lightweight native git status using direct git-index parsing.\n *\n * Parses `.git/index` to get tracked files and their stat-cache entries,\n * then compares against the working directory using file size and mtime.\n * Orders of magnitude faster than isomorphic-git's `statusMatrix` because:\n * - No JS↔Native bridge round-trips per file\n * - Uses stat-cache (size+mtime) instead of SHA-1 re-hashing\n * - Parallel file walking in Java\n *\n * @param gitRootDir The root directory of the git repository (parent of .git/)\n * @returns JSON string: `[{\"path\":\"tiddlers/foo.tid\",\"type\":\"add\"|\"modify\"|\"delete\"}, ...]`\n */\n gitStatus(gitRootDir: string): Promise<string>;\n\n /**\n * Debug function returning diagnostic info about the git repository state.\n * @returns JSON string with root/gitDir/index existence and git dir children\n */\n gitStatusDebug(gitRootDir: string): Promise<string>;\n\n /**\n * Build `.git/index` natively by reading the HEAD tree from pack files,\n * stat'ing all files on disk, and writing a v2 index file.\n *\n * This is used after archive clone where TidGi Desktop's tar export\n * doesn't include `.git/index`.\n *\n * @param gitRootDir The root directory of the git repository (parent of .git/)\n * @returns JSON string: `{\"ok\":true,\"entries\":N,\"indexSize\":M}` or `{\"ok\":false,\"error\":\"...\"}`\n */\n buildGitIndex(gitRootDir: string): Promise<string>;\n\n /**\n * Push local branch to remote using native JGit (efficient pack building).\n * Avoids OOM from isomorphic-git's JS-based pack construction on large repos.\n *\n * @param gitRootDir Absolute path to the git working directory\n * @param remoteName Remote name (e.g. \"origin\")\n * @param localBranch Local branch name (e.g. \"main\")\n * @param remoteBranch Remote branch ref (e.g. \"refs/heads/mobile-incoming\")\n * @param force Whether to force push\n * @param headers Optional HTTP headers as JSON string\n * @returns JSON string: `{\"ok\":true,\"updates\":[...]}` or `{\"ok\":false,\"error\":\"...\"}`\n */\n gitPush(gitRootDir: string, remoteName: string, localBranch: string, remoteBranch: string, force: boolean, headers?: string | null): Promise<string>;\n\n /**\n * Fetch from remote using native JGit (efficient pack handling).\n *\n * @param gitRootDir Absolute path to the git working directory\n * @param remoteName Remote name (e.g. \"origin\")\n * @param branch Branch to fetch\n * @param headers Optional HTTP headers as JSON string\n * @returns JSON string: `{\"ok\":true,\"updates\":[...]}` or `{\"ok\":false,\"error\":\"...\"}`\n */\n gitFetch(gitRootDir: string, remoteName: string, branch: string, headers?: string | null): Promise<string>;\n\n /**\n * Compare two commits and checkout only changed/new files to the working tree.\n * Avoids full checkout which OOMs on large repos.\n *\n * @param gitRootDir Absolute path to the git working directory\n * @param oldOid Old commit SHA (before fetch)\n * @param newOid New commit SHA (after fetch)\n * @returns JSON string: `{\"ok\":true,\"count\":N,\"files\":[...]}` or `{\"ok\":false,\"error\":\"...\"}`\n */\n gitCheckoutChangedFiles(gitRootDir: string, oldOid: string, newOid: string): Promise<string>;\n\n /**\n * Stage all changes and commit using native git (JGit on Android).\n * Replaces isomorphic-git's statusMatrix + add/remove loop which OOMs on large repos.\n *\n * @param gitRootDir Absolute path to the git working directory\n * @param message Commit message\n * @param authorName Author name\n * @param authorEmail Author email\n * @returns JSON string: `{\"ok\":true,\"commitId\":\"abc123\"}` or `{\"ok\":false,\"error\":\"...\"}`\n */\n gitAddAndCommit(gitRootDir: string, message: string, authorName: string, authorEmail: string): Promise<string>;\n\n /**\n * Reset the current branch to a specific ref using native git (JGit on Android).\n * Supports hard, mixed, and soft reset modes.\n *\n * @param gitRootDir Absolute path to the git working directory\n * @param ref Target ref (e.g. \"origin/main\", commit SHA)\n * @param mode Reset mode: \"hard\", \"mixed\", or \"soft\"\n * @returns JSON string: `{\"ok\":true,\"ref\":\"abc123\"}` or `{\"ok\":false,\"error\":\"...\"}`\n */\n gitReset(gitRootDir: string, ref: string, mode: string): Promise<string>;\n\n /**\n * Clone a remote repository using native JGit.\n *\n * @param url Remote repository URL\n * @param directory Destination directory\n * @param branch Branch to clone (null for default)\n * @param depth Depth for shallow clone (0 for full)\n * @param singleBranch Whether to clone only the specified branch\n * @param noTags Whether to skip fetching tags\n * @param headers Optional HTTP headers as JSON string\n * @returns JSON string: `{\"ok\":true,\"head\":\"abc123\"}` or `{\"ok\":false,\"error\":\"...\"}`\n */\n gitClone(url: string, directory: string, branch: string | null, depth: number, singleBranch: boolean, noTags: boolean, headers?: string | null): Promise<string>;\n\n /**\n * Get commit history using native git log.\n *\n * @param gitRootDir Absolute path to the git working directory\n * @param ref Branch or ref to start from (null for all)\n * @param maxCount Maximum number of commits to return\n * @returns JSON string: `{\"ok\":true,\"commits\":[{\"oid\",\"message\",\"authorName\",\"authorEmail\",\"timestamp\",\"parentOids\"}]}`\n */\n gitLog(gitRootDir: string, ref: string | null, maxCount: number): Promise<string>;\n\n /**\n * Resolve a git reference to its SHA-1 hash.\n *\n * @param gitRootDir Absolute path to the git working directory\n * @param ref Reference to resolve (e.g. \"HEAD\", \"refs/heads/main\", \"origin/main\")\n * @returns JSON string: `{\"ok\":true,\"oid\":\"abc123\"}` or `{\"ok\":false,\"error\":\"...\"}`\n */\n gitResolveRef(gitRootDir: string, ref: string): Promise<string>;\n\n /**\n * Get current branch name and branch listings.\n *\n * @param gitRootDir Absolute path to the git working directory\n * @returns JSON string: `{\"ok\":true,\"branch\":\"main\",\"isDetached\":false,\"localBranches\":[],\"remoteBranches\":[]}`\n */\n gitCurrentBranch(gitRootDir: string): Promise<string>;\n\n /**\n * Initialize a new git repository.\n *\n * @param directory Directory to initialize\n * @param defaultBranch Default branch name (e.g. \"main\")\n * @returns JSON string: `{\"ok\":true}` or `{\"ok\":false,\"error\":\"...\"}`\n */\n gitInit(directory: string, defaultBranch: string): Promise<string>;\n\n /**\n * Set a git config value.\n *\n * @param gitRootDir Absolute path to the git working directory\n * @param section Config section (e.g. \"remote\")\n * @param subsection Config subsection (e.g. \"origin\"), null for no subsection\n * @param name Config key (e.g. \"url\")\n * @param value Config value\n * @returns JSON string: `{\"ok\":true}` or `{\"ok\":false,\"error\":\"...\"}`\n */\n gitSetConfig(gitRootDir: string, section: string, subsection: string | null, name: string, value: string): Promise<string>;\n\n /**\n * Add a remote to the repository.\n *\n * @param gitRootDir Absolute path to the git working directory\n * @param remoteName Remote name (e.g. \"origin\")\n * @param url Remote URL\n * @returns JSON string: `{\"ok\":true}` or `{\"ok\":false,\"error\":\"...\"}`\n */\n gitAddRemote(gitRootDir: string, remoteName: string, url: string): Promise<string>;\n\n /**\n * Read a file (blob) at a specific commit reference.\n *\n * @param gitRootDir Absolute path to the git working directory\n * @param ref Commit ref to read from\n * @param filepath Relative path within the repository\n * @param asBase64 If true, return content as base64; otherwise as utf8 string\n * @returns JSON string: `{\"ok\":true,\"content\":\"...\",\"encoding\":\"base64\"|\"utf8\",\"size\":N}`\n */\n gitReadBlob(gitRootDir: string, ref: string, filepath: string, asBase64: boolean): Promise<string>;\n\n /**\n * Diff two commits and return changed files list.\n * Native equivalent of tree-walking diff.\n *\n * @param gitRootDir Absolute path to the git working directory\n * @param oldRef Old commit ref\n * @param newRef New commit ref\n * @returns JSON string: `{\"ok\":true,\"files\":[{\"path\":\"...\",\"type\":\"add\"|\"modify\"|\"delete\"}]}`\n */\n gitDiffTrees(gitRootDir: string, oldRef: string, newRef: string): Promise<string>;\n\n /**\n * Discard uncommitted changes for a specific file.\n * Checks out the HEAD version, or deletes the file if it's untracked.\n *\n * @param gitRootDir Absolute path to the git working directory\n * @param filepath Relative path of the file to discard\n * @returns JSON string: `{\"ok\":true,\"action\":\"checkout\"|\"delete\"}` or `{\"ok\":false,\"error\":\"...\"}`\n */\n gitDiscardFileChanges(gitRootDir: string, filepath: string): Promise<string>;\n}\n\nexport const ExternalStorage: IExternalStorageModule = new Proxy({} as IExternalStorageModule, {\n get(_target, property) {\n const mod = getNativeModule();\n return (mod as unknown as Record<string | symbol, unknown>)[property];\n },\n});\n\n/**\n * Strip file:// prefix from a URI to produce a plain filesystem path.\n * Safe to call on paths that are already plain.\n */\nexport function toPlainPath(uriOrPath: string): string {\n if (uriOrPath.startsWith('file://')) {\n return uriOrPath.slice('file://'.length);\n }\n return uriOrPath;\n}\n\nexport type { BatchWriteResult, DownloadFileResumableResult, ExtractTarResult, FileInfo, HttpPostToFileResult, IExternalStorageModule, ReadFileChunkResult };\n"]}
|
|
@@ -243,6 +243,46 @@ public class ExternalStorageModule: Module {
|
|
|
243
243
|
AsyncFunction("gitReset") { (gitRootDir: String, ref: String, mode: String) -> String in
|
|
244
244
|
try GitHelper.gitReset(gitRootDir: gitRootDir, ref: ref, mode: mode)
|
|
245
245
|
}
|
|
246
|
+
|
|
247
|
+
AsyncFunction("gitClone") { (url: String, directory: String, branch: String?, depth: Int, singleBranch: Bool, noTags: Bool, headers: String?) -> String in
|
|
248
|
+
try GitHelper.gitClone(url: url, directory: directory, branch: branch, depth: depth, singleBranch: singleBranch, noTags: noTags, headers: headers)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
AsyncFunction("gitLog") { (gitRootDir: String, ref: String?, maxCount: Int) -> String in
|
|
252
|
+
try GitHelper.gitLog(gitRootDir: gitRootDir, ref: ref, maxCount: maxCount)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
AsyncFunction("gitResolveRef") { (gitRootDir: String, ref: String) -> String in
|
|
256
|
+
try GitHelper.gitResolveRef(gitRootDir: gitRootDir, ref: ref)
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
AsyncFunction("gitCurrentBranch") { (gitRootDir: String) -> String in
|
|
260
|
+
try GitHelper.gitCurrentBranch(gitRootDir: gitRootDir)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
AsyncFunction("gitInit") { (directory: String, defaultBranch: String) -> String in
|
|
264
|
+
try GitHelper.gitInit(directory: directory, defaultBranch: defaultBranch)
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
AsyncFunction("gitSetConfig") { (gitRootDir: String, section: String, subsection: String?, name: String, value: String) -> String in
|
|
268
|
+
try GitHelper.gitSetConfig(gitRootDir: gitRootDir, section: section, subsection: subsection, name: name, value: value)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
AsyncFunction("gitAddRemote") { (gitRootDir: String, remoteName: String, url: String) -> String in
|
|
272
|
+
try GitHelper.gitAddRemote(gitRootDir: gitRootDir, remoteName: remoteName, url: url)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
AsyncFunction("gitReadBlob") { (gitRootDir: String, ref: String, filepath: String, asBase64: Bool) -> String in
|
|
276
|
+
try GitHelper.gitReadBlob(gitRootDir: gitRootDir, ref: ref, filepath: filepath, asBase64: asBase64)
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
AsyncFunction("gitDiffTrees") { (gitRootDir: String, oldRef: String, newRef: String) -> String in
|
|
280
|
+
try GitHelper.gitDiffTrees(gitRootDir: gitRootDir, oldRef: oldRef, newRef: newRef)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
AsyncFunction("gitDiscardFileChanges") { (gitRootDir: String, filepath: String) -> String in
|
|
284
|
+
try GitHelper.gitDiscardFileChanges(gitRootDir: gitRootDir, filepath: filepath)
|
|
285
|
+
}
|
|
246
286
|
}
|
|
247
287
|
|
|
248
288
|
// ─── Static helpers ──────────────────────────────────────────────
|
package/ios/GitHelper.swift
CHANGED
|
@@ -725,6 +725,68 @@ enum GitHelper {
|
|
|
725
725
|
static func gitReset(
|
|
726
726
|
gitRootDir: String, ref: String, mode: String
|
|
727
727
|
) throws -> String {
|
|
728
|
-
return "{\"ok\":false,\"error\":\"Native git reset not available on iOS (no libgit2)
|
|
728
|
+
return "{\"ok\":false,\"error\":\"Native git reset not available on iOS (no libgit2).\"}"
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
static func gitClone(
|
|
732
|
+
url: String, directory: String, branch: String?, depth: Int,
|
|
733
|
+
singleBranch: Bool, noTags: Bool, headers: String?
|
|
734
|
+
) throws -> String {
|
|
735
|
+
return "{\"ok\":false,\"error\":\"Native git clone not available on iOS (no libgit2).\"}"
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
static func gitLog(
|
|
739
|
+
gitRootDir: String, ref: String?, maxCount: Int
|
|
740
|
+
) throws -> String {
|
|
741
|
+
return "{\"ok\":false,\"error\":\"Native git log not available on iOS (no libgit2).\"}"
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
static func gitResolveRef(
|
|
745
|
+
gitRootDir: String, ref: String
|
|
746
|
+
) throws -> String {
|
|
747
|
+
return "{\"ok\":false,\"error\":\"Native git resolveRef not available on iOS (no libgit2).\"}"
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
static func gitCurrentBranch(
|
|
751
|
+
gitRootDir: String
|
|
752
|
+
) throws -> String {
|
|
753
|
+
return "{\"ok\":false,\"error\":\"Native git currentBranch not available on iOS (no libgit2).\"}"
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
static func gitInit(
|
|
757
|
+
directory: String, defaultBranch: String
|
|
758
|
+
) throws -> String {
|
|
759
|
+
return "{\"ok\":false,\"error\":\"Native git init not available on iOS (no libgit2).\"}"
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
static func gitSetConfig(
|
|
763
|
+
gitRootDir: String, section: String, subsection: String?,
|
|
764
|
+
name: String, value: String
|
|
765
|
+
) throws -> String {
|
|
766
|
+
return "{\"ok\":false,\"error\":\"Native git setConfig not available on iOS (no libgit2).\"}"
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
static func gitAddRemote(
|
|
770
|
+
gitRootDir: String, remoteName: String, url: String
|
|
771
|
+
) throws -> String {
|
|
772
|
+
return "{\"ok\":false,\"error\":\"Native git addRemote not available on iOS (no libgit2).\"}"
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
static func gitReadBlob(
|
|
776
|
+
gitRootDir: String, ref: String, filepath: String, asBase64: Bool
|
|
777
|
+
) throws -> String {
|
|
778
|
+
return "{\"ok\":false,\"error\":\"Native git readBlob not available on iOS (no libgit2).\"}"
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
static func gitDiffTrees(
|
|
782
|
+
gitRootDir: String, oldRef: String, newRef: String
|
|
783
|
+
) throws -> String {
|
|
784
|
+
return "{\"ok\":false,\"error\":\"Native git diffTrees not available on iOS (no libgit2).\"}"
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
static func gitDiscardFileChanges(
|
|
788
|
+
gitRootDir: String, filepath: String
|
|
789
|
+
) throws -> String {
|
|
790
|
+
return "{\"ok\":false,\"error\":\"Native git discardFileChanges not available on iOS (no libgit2).\"}"
|
|
729
791
|
}
|
|
730
792
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-tiddlywiki-filesystem-android-external-storage",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
4
4
|
"description": "Expo native module for TidGi-Mobile: filesystem I/O + TiddlyWiki .tid/.meta/.json batch parsing + git status in Kotlin (Android) and Swift (iOS)",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
package/src/index.ts
CHANGED
|
@@ -265,6 +265,110 @@ interface IExternalStorageModule {
|
|
|
265
265
|
* @returns JSON string: `{"ok":true,"ref":"abc123"}` or `{"ok":false,"error":"..."}`
|
|
266
266
|
*/
|
|
267
267
|
gitReset(gitRootDir: string, ref: string, mode: string): Promise<string>;
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Clone a remote repository using native JGit.
|
|
271
|
+
*
|
|
272
|
+
* @param url Remote repository URL
|
|
273
|
+
* @param directory Destination directory
|
|
274
|
+
* @param branch Branch to clone (null for default)
|
|
275
|
+
* @param depth Depth for shallow clone (0 for full)
|
|
276
|
+
* @param singleBranch Whether to clone only the specified branch
|
|
277
|
+
* @param noTags Whether to skip fetching tags
|
|
278
|
+
* @param headers Optional HTTP headers as JSON string
|
|
279
|
+
* @returns JSON string: `{"ok":true,"head":"abc123"}` or `{"ok":false,"error":"..."}`
|
|
280
|
+
*/
|
|
281
|
+
gitClone(url: string, directory: string, branch: string | null, depth: number, singleBranch: boolean, noTags: boolean, headers?: string | null): Promise<string>;
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Get commit history using native git log.
|
|
285
|
+
*
|
|
286
|
+
* @param gitRootDir Absolute path to the git working directory
|
|
287
|
+
* @param ref Branch or ref to start from (null for all)
|
|
288
|
+
* @param maxCount Maximum number of commits to return
|
|
289
|
+
* @returns JSON string: `{"ok":true,"commits":[{"oid","message","authorName","authorEmail","timestamp","parentOids"}]}`
|
|
290
|
+
*/
|
|
291
|
+
gitLog(gitRootDir: string, ref: string | null, maxCount: number): Promise<string>;
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Resolve a git reference to its SHA-1 hash.
|
|
295
|
+
*
|
|
296
|
+
* @param gitRootDir Absolute path to the git working directory
|
|
297
|
+
* @param ref Reference to resolve (e.g. "HEAD", "refs/heads/main", "origin/main")
|
|
298
|
+
* @returns JSON string: `{"ok":true,"oid":"abc123"}` or `{"ok":false,"error":"..."}`
|
|
299
|
+
*/
|
|
300
|
+
gitResolveRef(gitRootDir: string, ref: string): Promise<string>;
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Get current branch name and branch listings.
|
|
304
|
+
*
|
|
305
|
+
* @param gitRootDir Absolute path to the git working directory
|
|
306
|
+
* @returns JSON string: `{"ok":true,"branch":"main","isDetached":false,"localBranches":[],"remoteBranches":[]}`
|
|
307
|
+
*/
|
|
308
|
+
gitCurrentBranch(gitRootDir: string): Promise<string>;
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Initialize a new git repository.
|
|
312
|
+
*
|
|
313
|
+
* @param directory Directory to initialize
|
|
314
|
+
* @param defaultBranch Default branch name (e.g. "main")
|
|
315
|
+
* @returns JSON string: `{"ok":true}` or `{"ok":false,"error":"..."}`
|
|
316
|
+
*/
|
|
317
|
+
gitInit(directory: string, defaultBranch: string): Promise<string>;
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Set a git config value.
|
|
321
|
+
*
|
|
322
|
+
* @param gitRootDir Absolute path to the git working directory
|
|
323
|
+
* @param section Config section (e.g. "remote")
|
|
324
|
+
* @param subsection Config subsection (e.g. "origin"), null for no subsection
|
|
325
|
+
* @param name Config key (e.g. "url")
|
|
326
|
+
* @param value Config value
|
|
327
|
+
* @returns JSON string: `{"ok":true}` or `{"ok":false,"error":"..."}`
|
|
328
|
+
*/
|
|
329
|
+
gitSetConfig(gitRootDir: string, section: string, subsection: string | null, name: string, value: string): Promise<string>;
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Add a remote to the repository.
|
|
333
|
+
*
|
|
334
|
+
* @param gitRootDir Absolute path to the git working directory
|
|
335
|
+
* @param remoteName Remote name (e.g. "origin")
|
|
336
|
+
* @param url Remote URL
|
|
337
|
+
* @returns JSON string: `{"ok":true}` or `{"ok":false,"error":"..."}`
|
|
338
|
+
*/
|
|
339
|
+
gitAddRemote(gitRootDir: string, remoteName: string, url: string): Promise<string>;
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Read a file (blob) at a specific commit reference.
|
|
343
|
+
*
|
|
344
|
+
* @param gitRootDir Absolute path to the git working directory
|
|
345
|
+
* @param ref Commit ref to read from
|
|
346
|
+
* @param filepath Relative path within the repository
|
|
347
|
+
* @param asBase64 If true, return content as base64; otherwise as utf8 string
|
|
348
|
+
* @returns JSON string: `{"ok":true,"content":"...","encoding":"base64"|"utf8","size":N}`
|
|
349
|
+
*/
|
|
350
|
+
gitReadBlob(gitRootDir: string, ref: string, filepath: string, asBase64: boolean): Promise<string>;
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Diff two commits and return changed files list.
|
|
354
|
+
* Native equivalent of tree-walking diff.
|
|
355
|
+
*
|
|
356
|
+
* @param gitRootDir Absolute path to the git working directory
|
|
357
|
+
* @param oldRef Old commit ref
|
|
358
|
+
* @param newRef New commit ref
|
|
359
|
+
* @returns JSON string: `{"ok":true,"files":[{"path":"...","type":"add"|"modify"|"delete"}]}`
|
|
360
|
+
*/
|
|
361
|
+
gitDiffTrees(gitRootDir: string, oldRef: string, newRef: string): Promise<string>;
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Discard uncommitted changes for a specific file.
|
|
365
|
+
* Checks out the HEAD version, or deletes the file if it's untracked.
|
|
366
|
+
*
|
|
367
|
+
* @param gitRootDir Absolute path to the git working directory
|
|
368
|
+
* @param filepath Relative path of the file to discard
|
|
369
|
+
* @returns JSON string: `{"ok":true,"action":"checkout"|"delete"}` or `{"ok":false,"error":"..."}`
|
|
370
|
+
*/
|
|
371
|
+
gitDiscardFileChanges(gitRootDir: string, filepath: string): Promise<string>;
|
|
268
372
|
}
|
|
269
373
|
|
|
270
374
|
export const ExternalStorage: IExternalStorageModule = new Proxy({} as IExternalStorageModule, {
|