هدف این پست اینه که نوتهات (مثلا Obsidian) هر ۱۵ دقیقه بررسی بشن و اگر تغییری داشتند، خودکار commit و push بشن توی یک ریپازیتوری Private در GitHub.
این کار عملا دو تا نتیجهی مهم داره:
- بکاپ امن و نسخهبندیشده داری (هر تغییر تاریخچه داره)
- لازم نیست هر بار دستی push کنی
قبل از شروع، خیلی کوتاه فرضهای این آموزش:
- سیستم عامل: Windows 10 یا Windows 11
- نوتها داخل یک فولدر مشخص هستند (مثلا
D:\Obsidian) - اکانت GitHub داری
- Git روی ویندوز نصب میشه و از خط فرمان قابل استفادهست
ساخت ریپازیتوری در GitHub
- وارد GitHub شو
- روی New repository بزن
- یک اسم برای repo بده (مثلا
obsidian-notesیا هر چی دوست داری) - گزینهی Private رو انتخاب کن
- Create repository رو بزن (GitHub Docs)
نکته مهم:
- فعلا README و .gitignore و License رو از GitHub نساز (اختیاریه)، چون ما میخوایم فولدر فعلی رو push کنیم.
تبدیل فولدر به repo و اتصال به GitHub
یک PowerShell باز کن و اینها رو مرحله به مرحله انجام بده.
رفتن داخل فولدر
cd D:\Obsidian
ساخت git repo محلی
git init
ساخت .gitignore
چون قراره اسکریپت ما فایل لاگ بسازه، اگر لاگها رو ignore نکنی، ممکنه همیشه تغییر تشخیص داده بشه و بیخودی commit بزنه.
یک فایل به اسم .gitignore داخل D:\Obsidian بساز و اینها رو داخلش بذار:
.obsidian/workspace.json
.obsidian/cache/
.trash/
.logs/
*.log
3.4 اولین commit
git add -A
git commit -m "Initial commit"
3.5 افزودن remote
برگرد به صفحهی repo توی GitHub:
- روی دکمهی Code بزن
- بخش HTTPS رو انتخاب کن
- URL رو Copy کن (مثلا چیزی شبیه
https://github.com/.../... .git) حالا در PowerShell این دستور رو بزن (این ترفند، URL رو از Clipboard برمیداره):
git remote add origin (Get-Clipboard)
برای اطمینان:
git remote -v
push اولیه و ذخیره شدن لاگین
بهتره branch رو main کنیم (استاندارد جدید بیشتر repo ها همینه):
git branch -M main
حالا push:
git push -u origin main
این مرحله ممکنه:
- یک پنجره مرورگر برای لاگین باز کنه
- یا ازت اجازه دسترسی بگیره
- یا اطلاعات رو ذخیره کنه
هدف اینه که یک بار لاگین انجام بشه و بعدش دفعات بعدی push بدون سوال کار کنه. (GitHub Docs)
قدم 4: نوشتن اسکریپت برای Auto Commit & Push
ایده اسکریپت:
مطمئن میشه مسیر درست و repo معتبره
git status --porcelainرو میخونهاگر تغییری نبود، خارج میشه
اگر تغییر بود، add → commit → push
همه چیز رو توی فایل لاگ مینویسه
4.1 ساخت فولدر اسکریپت
داخل repo یک فولدر بساز:
D:\Obsidian\.scripts
و فایل زیر رو داخلش بساز:
D:\Obsidian\.scripts\auto-git-push.ps1
4.2 کد کامل اسکریپت
این نسخه هم سادهست، هم برای لاگ گرفتن مناسب، هم دردسر Quote کردن آرگومانها رو نداره:
# ================================
# Auto Git Commit & Push Script (beginner-friendly + robust logging)
# Repo: D:\Obsidian
# Script: D:\Obsidian\.scripts\auto-git-push.ps1
# Logs: D:\Obsidian\.logs\auto-git-push_YYYY-MM-DD.log
# ================================
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
$RepoPath = "D:\Obsidian"
$LogDir = Join-Path $RepoPath ".logs"
$LogFile = Join-Path $LogDir ("auto-git-push_{0}.log" -f (Get-Date -Format "yyyy-MM-dd"))
function Ensure-Dir([string]$Path) {
if (-not (Test-Path -LiteralPath $Path -PathType Container)) {
New-Item -Path $Path -ItemType Directory -Force | Out-Null
}
}
function Write-Log {
param(
[Parameter(Mandatory = $true)][string]$Message,
[ValidateSet("INFO","WARN","ERROR")][string]$Level = "INFO"
)
$ts = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$line = "[{0}] [{1}] {2}" -f $ts, $Level, $Message
Write-Host $line
Add-Content -Path $LogFile -Value $line -Encoding UTF8
}
function Invoke-Git {
param(
[Parameter(Mandatory = $true)][string[]]$Args
)
# Run git in repo directory; capture both stdout/stderr
$output = & git -C $RepoPath @Args 2>&1
$exit = $LASTEXITCODE
return [pscustomobject]@{
ExitCode = $exit
Output = ($output | Out-String)
Command = ("git -C " + $RepoPath + " " + ($Args -join " "))
}
}
try {
if (-not (Test-Path -LiteralPath $RepoPath -PathType Container)) {
throw "Repository path not found: $RepoPath"
}
Ensure-Dir $LogDir
Write-Log "========== Auto Git Push START =========="
$gitCmd = Get-Command git -ErrorAction SilentlyContinue
if ($null -eq $gitCmd) {
throw "git is not available in PATH"
}
Write-Log "Git detected: $($gitCmd.Source)"
# Ensure repo
$r = Invoke-Git @("rev-parse","--is-inside-work-tree")
if ($r.ExitCode -ne 0 -or $r.Output.Trim() -ne "true") {
Write-Log $r.Command "ERROR"
if ($r.Output) { Write-Log ($r.Output.Trim()) "ERROR" }
throw "This directory is not a git repository"
}
# Current branch
$b = Invoke-Git @("branch","--show-current")
if ($b.ExitCode -ne 0) {
Write-Log $b.Command "ERROR"
if ($b.Output) { Write-Log ($b.Output.Trim()) "ERROR" }
throw "Cannot detect current branch"
}
$branch = $b.Output.Trim()
Write-Log "Current branch: $branch"
# Remote exists?
$rem = Invoke-Git @("remote")
if ($rem.ExitCode -ne 0) {
Write-Log $rem.Command "ERROR"
if ($rem.Output) { Write-Log ($rem.Output.Trim()) "ERROR" }
throw "Cannot read git remotes"
}
if ([string]::IsNullOrWhiteSpace($rem.Output)) {
throw "No git remote configured"
}
Write-Log ("Remote(s): " + ($rem.Output.Trim() -replace "\r?\n", ", "))
# Refresh index
[void](Invoke-Git @("update-index","-q","--refresh"))
# Check changes
$st = Invoke-Git @("status","--porcelain")
if ($st.ExitCode -ne 0) {
Write-Log $st.Command "ERROR"
if ($st.Output) { Write-Log ($st.Output.Trim()) "ERROR" }
throw "git status failed"
}
if ([string]::IsNullOrWhiteSpace($st.Output)) {
Write-Log "No changes detected. Nothing to commit."
Write-Log "========== Auto Git Push END =========="
exit 0
}
Write-Log "Changes detected:"
$st.Output -split "`r?`n" | ForEach-Object {
if (-not [string]::IsNullOrWhiteSpace($_)) { Write-Log (" " + $_.Trim()) }
}
# Stage
$add = Invoke-Git @("add","-A")
if ($add.ExitCode -ne 0) {
Write-Log $add.Command "ERROR"
if ($add.Output) { Write-Log ($add.Output.Trim()) "ERROR" }
throw "git add failed"
}
Write-Log "All changes staged"
# Commit
$commitMsg = "Auto update: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
$cm = Invoke-Git @("commit","-m",$commitMsg)
if ($cm.ExitCode -ne 0) {
Write-Log $cm.Command "ERROR"
if ($cm.Output) { Write-Log ($cm.Output.Trim()) "ERROR" }
throw "git commit failed"
}
if ($cm.Output) { Write-Log ($cm.Output.Trim()) }
Write-Log "Committed with message: $commitMsg"
# Push
Write-Log "Pushing to remote..."
$ps = Invoke-Git @("push")
Write-Log $ps.Command
if ($ps.Output) {
($ps.Output -split "`r?`n") | ForEach-Object { if($_){ Write-Log $_ } }
}
if ($ps.ExitCode -ne 0) {
throw "git push failed (ExitCode=$($ps.ExitCode))"
}
Write-Log "Push successful"
Write-Log "========== Auto Git Push END =========="
exit 0
}
catch {
Write-Log ("FATAL ERROR: " + $_.Exception.Message) "ERROR"
Write-Log (($_ | Out-String).Trim()) "ERROR"
Write-Log "========== Auto Git Push FAILED =========="
exit 1
}
نکتهی مهم: چرا .logs/ رو ignore کردیم؟
چون اسکریپت هر بار داخل .logs مینویسه؛ اگر ignore نکنی، خودش باعث “تغییر” میشه و ممکنه هر بار commit بزنه حتی اگر نوتها تغییر نکرده باشن.
قدم 5: تست دستی اسکریپت
قبل از Task Scheduler، یک بار دستی اجراش کن:
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "D:\Obsidian\.scripts\auto-git-push.ps1"
اگر اولین بار باشه، ممکنه لازم باشه همون دفعهی اول لاگین GitHub کامل انجام بشه (مرورگر باز بشه). (GitHub Docs)
بعد از اجرا:
فایل لاگ رو ببین:
D:\Obsidian\.logs\auto-git-push_YYYY-MM-DD.logصفحه GitHub repo رو refresh کن و ببین commit جدید اومده یا نه
قدم 6: تنظیم Task Scheduler برای اجرای هر 15 دقیقه
هدف اینه که ویندوز هر ۱۵ دقیقه اسکریپت رو اجرا کنه.
6.1 باز کردن Task Scheduler
Start Menu رو باز کن و سرچ کن: Task Scheduler
و اجراش کن.
6.2 ساخت Task جدید
روی Create Task بزن (نه Create Basic Task، چون تنظیمات دقیقتر میخوایم).
تب General
Name: مثلا
Auto Git Push (Obsidian)گزینهی Run whether user is logged on or not رو انتخاب کن (اگر میخوای حتی وقتی لاگآوتی هم اجرا بشه)
تیک Run with highest privileges رو هم بزن (برای جلوگیری از بعضی خطاهای دسترسی)
تب Triggers
New
Begin the task: On a schedule
Daily
Start: یک زمان دلخواه (مثلا 09:00)
تیک Repeat task every:
15 minutesfor a duration of:
1 dayOK
این مدل تنظیم باعث میشه هر روز تکرار ۱۵ دقیقهای داشته باشی.
تب Actions
New
Action: Start a program
Program/script:
powershell.exe
- Add arguments:
-NoProfile -ExecutionPolicy Bypass -File "D:\Obsidian\.scripts\auto-git-push.ps1"
- Start in (optional):
D:\Obsidian
OK
تب Conditions
برای اینکه وسط کار بیدلیل متوقف نشه:
اگر لپتاپ داری، تیک Start the task only if the computer is on AC power رو بردار
تیکهای Idle رو هم بردار (اگر نمیخوای وابسته به idle باشه)
تب Settings
این قسمت خیلی مهمه:
تیک Allow task to be run on demand روشن باشه
تیک Run task as soon as possible after a scheduled start is missed روشن باشه
گزینهی If the task is already running, then the following rule applies:
بذارش روی Do not start a new instance (این باعث میشه اگر یه اجرا طول کشید، اجرای بعدی همزمان نشه) (Super User)
در نهایت OK بزن. ممکنه ازت پسورد ویندوز رو بپرسه (اگر حالت run whether user… رو انتخاب کرده باشی).
6.3 تست از داخل Task Scheduler
روی Task ساخته شده راست کلیک کن و Run رو بزن.
بعد:
در تب History (اگر فعال باشه) وضعیت رو ببین
و مهمتر: فایل لاگ رو چک کن
چکلیست تست (مثل test case)
برای اینکه مطمئن شی همه چیز درست کار میکنه، این سناریوها رو تست کن:
1) هیچ تغییری نده → اسکریپت باید بنویسه "No changes detected" و exit 0
2) یک فایل .md جدید بساز → باید commit و push انجام بشه
3) یک فایل موجود رو تغییر بده → باید commit و push انجام بشه
4) یک فایل رو rename کن → باید تغییرات ثبت و push بشه
5) یک فایل رو حذف کن → باید حذف هم commit و push بشه
مشکلات رایج و رفعشان
1) خطای git is not available in PATH
Git درست نصب نشده یا PATH ست نشده
راه سریع: Git for Windows رو دوباره نصب کن (گزینههای پیشفرض)
2) موقع push خطای Authentication failed میاد
چند حالت رایج:
Git قدیمیه و Credential Manager درست OAuth نمیکنه → Git for Windows رو آپدیت کن (GitHub Docs)
یک بار باید دستی
git pushانجام بدی تا لاگین مرورگری و ذخیره شدن credential انجام بشه (GitHub Docs)اگر Credential های خراب ذخیره شده:
- Windows Credential Manager رو باز کن و credential مربوط به
git:https://github.comرو پاک/اصلاح کن (این کار معمولا مشکل رو حل میکنه) (Stack Overflow)
- Windows Credential Manager رو باز کن و credential مربوط به
راه جایگزین (اگر OAuth/Browser جواب نداد): Personal Access Token
اگر مجبور شدی از PAT استفاده کنی:
در GitHub برو بخش Personal access tokens و یک token بساز (ترجیحا fine-grained با دسترسی محدود) (GitHub Docs)
سپس موقع push، به جای Password، همون token رو وارد کن
نکته امنیتی: token رو داخل اسکریپت ذخیره نکن؛ بذار Credential Manager نگهش داره
(GitHub خودش fine-grained رو توصیه میکنه چون محدود و امنتره) (GitHub Docs)
3) Task Scheduler اجرا میشه ولی چیزی push نمیشه
اول لاگ رو چک کن:
D:\Obsidian\.logs\...اگر نوشته No changes detected، یعنی واقعا تغییر ندیدی یا فایلها خارج از repo تغییر کردند
اگر repoPath اشتباه باشه، توی لاگ میفهمی
4) Task اجرا نمیشه یا “0x1” میخوره
معمولا به خاطر مسیر اشتباه فایل ps1 یا permission هست
مسیر
-File "..."رو دقیقا چک کنRun with highest privileges رو روشن کن
یک بار با Run دستی تست کن
نکات امنیتی
ریپازیتوری رو Private نگه دار (این آموزش هم روی Private طراحی شده) (GitHub Docs)
هر چیزی که توی نوتهاست، فرض کن ممکنه روزی لو بره (مثلا اگر اشتباها repo رو Public کردی). پس:
- پسوردها، توکنها، کلیدها رو داخل نوتها ننویس
اگر از PAT استفاده کردی:
تاریخ انقضا بذار
فقط به همون repo دسترسی بده (fine-grained) (GitHub Docs)
اگر پیشنهادی برای اصلاح پست دارید، میتونید از طریق دکمهی "پیشنهاد اصلاح متن" وارد ریپازیتوری وبلاگ در github بشید و تغییرات مورد نظرتون رو پیشنهاد بدید.
منابع
مستندات ساخت ریپازیتوری در GitHub (GitHub Docs)
مستندات GitHub درباره ذخیره کردن credential ها (روی HTTPS) (GitHub Docs)
مستندات Git درباره credential helper و Git Credential Manager (Git)
توضیح گزینه “If the task is already running…” در Task Scheduler (Super User)
اگر پیشنهادی برای اصلاح پست دارید، میتونید از طریق دکمهی "پیشنهاد اصلاح متن" وارد ریپازیتوری وبلاگ در github بشید و تغییرات مورد نظرتون رو پیشنهاد بدید.