動機

因為我讀分科的時候都把筆記放在 obsidian 然後是用 fit(基於 GitHub)同步的,但是在 iOS 上就有問題(iPad 還沒有),GitHub issue 上是建議降級用舊版 但是舊版也會有問題,所以都越獄了我就乾脆寫 shell script 開搞

踩坑

  • fit 預設應該是不會備份 .obsidian 但大概是因為我之前直接用 git force pull 了,所以手機因為一些 plugins 不兼容然後就會一直重新 indexing,跟掛了沒兩樣
  • 又嘗試了其他 gitless 插件也不行
  • 找到 這個文章,使用了 working copy,但我還真不想付那個錢
    • 我還順便在 filza 亂翻他的架構的時候學到這個:
  • 這個 是用 a-shell 的方法 但看了就好麻煩
  • 考慮過越獄的 catscript 但是轉念一想 那為什麼不直接用 shellscript 肯定比 python 好

(用 Working Copy 的方案)

(用 a-shell 的方案)

Git 設置

因為前面講過 手機的 .obsidian 跟電腦的完全不能共用,所以我就想在 Git 設定好讓 . 開頭的資料都不跟著上傳下載

因為 .gitignore 只能防止被 add ,加上其他電腦裝置需要同步 .obsidian ,所以要改使用 別的方法

Sparse Checkout(稀疏檢出)

pull 或者 clone 東西下來的時候篩選哪些文件 / 檔案要到工作區/顯示出來
因為我是不要 . 開頭的文件 所以我就要在 .git/info/sparse-checkout 這樣寫:

1
2
/*
!/.*

然後是 .git/info/exclude ,這做的事情跟 .gitignore 一樣,但是只限本地的這個 repo,不會動到 remote 的設定 :

1
2
3
4
5
6
7
# git ls-files --others --exclude-from=.git/info/exclude
# Lines that start with '#' are comments.
# For a project mostly in C, the following would be a good set of
# exclude patterns (uncomment them if you want to use them):
# *.[oa]
# *~
.*

Powercuts

我後來就開始尋找有沒有可以集成越獄功能的捷徑工具,畢竟儘量少動一點底層的操作對手機比較安全,也是成功在 reddit 上面找到這篇文章:

他有很多實用的 shortcut,雖然我感覺可以用其他方式實現,但是我不想花太多時間(那時候要讀分科)所以我就直接用了 Run shell command 這個方塊,然後讓 AI 幫我生 shellscript,儘量能處理到大部分的情況:

  • remote 沒有改變:
    • local 沒改變:結束
    • local 有改變:直接 push
  • remote 有改變:
    • 能夠直接 merge:merge 後 push
    • 不能直接 merge:跳 error

當初真的沒有空去搞解 conflict 的東西,所以我就儘量不再手機上寫新東西,還有就是設定自動化在開關 obsidian 的時候跳通知問你要不要 sync,真的不行就開 terminal 自己拆,目前是沒什麼遇到問題,挺好。


Shortcut 架構

因為考慮到 shellscript 直接寫死在 shortcut 裡面好像不太好,所以我把程式碼放在檔案目錄下:

完整程式碼

⚠️注意:此程式主要是由 AI(Gemini)生成後我檢查的,所以請謹慎使用
尖括弧 <> 內的內容請改成自己的資料

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
#!/bin/bash

########################################
# CONFIG
########################################

REPO_PATH="<本機內 repo/vault 的位置>"
LOCK_FILE="/tmp/obsidian_git_sync.lock"
LOG_FILE="/var/mobile/obsidian_git_sync.log"

COMMIT_MESSAGE="iPhone Auto Sync"


CONFLICT_FILE="<本機內 repo/vault 的位置>/iphone_conflict.md"

########################################
# UTIL
########################################

log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$LOG_FILE"
}

########################################
# LOCK
########################################

if [ -f "$LOCK_FILE" ]; then
echo "BUSY: sync running"
exit 0
fi

touch "$LOCK_FILE"
trap 'rm -f "$LOCK_FILE"' EXIT

########################################
# ENTER REPO
########################################

cd "$REPO_PATH" 2>/dev/null || {
echo "ERROR: repo path not found"
exit 1
}

git rev-parse --is-inside-work-tree >/dev/null 2>&1 || {
echo "ERROR: not a git repo"
exit 1
}

########################################
# FETCH
########################################

FETCH_OUTPUT=$(git fetch 2>&1)
if [ $? -ne 0 ]; then
log "FETCH FAIL: $FETCH_OUTPUT"
echo "ERROR: fetch failed"
exit 1
fi

########################################
# LOCAL CHANGES → COMMIT
########################################

if [ -n "$(git status --porcelain)" ]; then
git add -A

COMMIT_OUTPUT=$(git commit -m "$COMMIT_MESSAGE" 2>&1)
if [ $? -ne 0 ]; then
log "COMMIT FAIL: $COMMIT_OUTPUT"
echo "ERROR: commit failed"
exit 1
fi
fi

########################################
# SHOW STATUS BEFORE MERGE
########################################

STATUS=$(git status --porcelain)

if [ -n "$STATUS" ]; then
echo "MODIFIED:"
echo "$STATUS" | awk '{print $2}'
fi

########################################
# MERGE
########################################

MERGE_OUTPUT=$(git merge origin/main 2>&1)
MERGE_STATUS=$?

########################################
# CONFLICT HANDLING
########################################

if [ $MERGE_STATUS -ne 0 ]; then

CONFLICT_FILES=$(git diff --name-only --diff-filter=U)
STATUS_BEFORE=$(git status --porcelain)

TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')

log "MERGE CONFLICT: $MERGE_OUTPUT"

########################################
# CREATE .conflict.md
########################################

{
echo "# Git Merge Conflict Report"
echo ""
echo "Time: $TIMESTAMP"
echo ""
echo "## Error"
echo "$MERGE_OUTPUT"
echo ""
echo "## Conflicted Files"
echo "$CONFLICT_FILES"
echo ""
echo "## Git Status (porcelain)"
echo "$STATUS_BEFORE"
} > "$CONFLICT_FILE"

########################################
# OUTPUT FOR POWERCUTS
########################################

echo "ERROR: merge conflict"
echo ""
echo "MODIFIED:"
echo "$CONFLICT_FILES"

exit 1
fi

########################################
# PUSH
########################################

PUSH_OUTPUT=$(git push 2>&1)
if [ $? -ne 0 ]; then
log "PUSH FAIL: $PUSH_OUTPUT"
echo "ERROR: push failed"
exit 1
fi

########################################
# SUCCESS
########################################

log "SYNC OK"

echo "OK: sync complete"
exit 0