Git Advanced
זרימות עבודה מקצועיות ב-Git: rebase, hooks, branching strategies, bisect ו-CI/CD — כפי שנדרש בצוותי DevOps בישראל
תיאוריה
מדוע Git מתקדם הוא דרישת סף בשוק הישראלי
Git בסיסי — clone, commit, push, pull-request — הוא נדרש מכל מתכנת. אבל ב-DevOps, Git הוא גם כלי תפעולי: הוא מגדיר מי מורשה לדחוף קוד, מה קורה לפני שכל commit נכנס, ואיך pipeline ה-CI/CD מתנהג. ב-2026, ראיונות DevOps בישראל בודקים האם המועמד יודע לנהל היסטוריית commits ב-production repositories, לאתר regression עם git bisect, לכתוב pre-commit hooks שאוכפים standards, ולהבין את ההבדל בין trunk-based development לבין GitFlow. הסעיפים הבאים מכסים בדיוק את הפערים האלה — כפי שנמצאו בסריקת שוק עבודה ובביקורת הידע.
Interactive Rebase: ניקוי היסטוריית commits לפני merge
כאשר עובדים על feature branch, מצטברים commits כמו 'fix typo', 'WIP', 'oops forgot import' — commits שלא שייכים להיסטוריה הרשמית של הפרויקט. Interactive rebase מאפשר לנקות, לדחוס ולשכתב את ה-commits האלה לפני שה-branch נכנס ל-main.
הפקודה הבסיסית:
# rebase interactive על 5 commits האחרונים
git rebase -i HEAD~5
# או על כל commits מאז שה-branch נפרד מ-main
git rebase -i $(git merge-base HEAD main)בתוך editor שנפתח, לכל commit יש פעולה:
- pick — שמור כמות שהוא
- squash (או s) — מזג עם ה-commit שלפניו, שמור שניהם בmessage
- fixup (או f) — מזג עם ה-commit שלפניו, מחק את ה-message הזה
- reword (או r) — שמור את ה-commit, שנה רק את ה-message
- drop (או d) — הסר את ה-commit לחלוטין
- edit (או e) — עצור אחרי ה-commit הזה, אפשר לשנות תוכן
דוגמה מעשית — לפני rebase:
pick a1b2c3 add user authentication feature
pick d4e5f6 fix typo in auth module
pick g7h8i9 WIP - still working
pick j0k1l2 forgot to add test file
pick m3n4o5 final cleanupאחרי עריכת rebase:
pick a1b2c3 add user authentication feature
fixup d4e5f6 fix typo in auth module
drop g7h8i9 WIP - still working
fixup j0k1l2 forgot to add test file
fixup m3n4o5 final cleanupתוצאה: commit יחיד נקי עם message 'add user authentication feature'.
אזהרה קריטית: **לעולם לא עושים rebase על commits שכבר ב-main/master** אם אנשים אחרים התבססו עליהם. rebase משכתב היסטוריה — זה בסדר על branch פרטי, מסוכן על shared branches.
אחרי rebase על branch פרטי שכבר push-ו:
# force push נדרש כי ה-history השתנה
git push --force-with-lease origin feature/auth
# --force-with-lease בטוח יותר מ-force: נכשל אם מישהו אחר push-ו לאותו branchGit Hooks: אכיפת standards לפני שקוד נכנס
Git hooks הם scripts שרצים אוטומטית בנקודות ספציפיות ב-git workflow. הם יושבים ב-.git/hooks/ ומאפשרים לאכוף linting, tests, ו-security checks לפני שכל commit או push יתבצע.
שני hooks קריטיים ב-production:
**1. pre-commit** — רץ לפני שה-commit נוצר. אם יוצא עם exit code שאינו 0, ה-commit נבלם:
#!/usr/bin/env bash
set -euo pipefail
# בדיקת linting
echo 'Running linting checks...'
npx eslint --ext .ts,.tsx src/ || {
echo 'ESLint failed. Fix errors before committing.'
exit 1
}
# בדיקת secret leaks — מונע push של AWS keys בטעות
if git diff --cached | grep -qE '(AKIA[A-Z0-9]{16}|aws_secret_access_key)'; then
echo 'ERROR: Possible AWS credentials detected in staged changes!'
exit 1
fi
echo 'pre-commit: all checks passed.'התקנה:
# שמור ב-.git/hooks/pre-commit
cp scripts/pre-commit.sh .git/hooks/pre-commit
chmod +x .git/hooks/pre-commit**2. pre-push** — רץ לפני שה-push נשלח לremote. מתאים להרצת tests שלוקחים יותר זמן:
#!/usr/bin/env bash
set -euo pipefail
PROTECTED_BRANCH='main'
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
# חסום push ישיר ל-main
if [ "$CURRENT_BRANCH" = "$PROTECTED_BRANCH" ]; then
echo 'ERROR: Direct push to main is not allowed. Open a PR instead.'
exit 1
fi
# הרץ tests לפני push
echo 'Running test suite...'
npm test || {
echo 'Tests failed. Fix before pushing.'
exit 1
}**ניהול hooks בצוות עם husky:**
הבעיה: .git/hooks/ לא ב-version control. כל מפתח צריך להתקין hooks ידנית — זה לא מעשי.
הפתרון: husky — מנהל hooks שמאפשר לשמור hooks ב-.husky/ שנמצא ב-repository:
npm install --save-dev husky
npx husky init
# .husky/pre-commit נוצר ונמצא תחת gitקובץ .husky/pre-commit:
npx lint-stagedעם lint-staged ב-package.json:
{
"lint-staged": {
"*.{ts,tsx}": ["eslint --fix", "prettier --write"],
"*.{py}": ["black .", "flake8"]
}
}Trunk-Based Development מול GitFlow: בחירת האסטרטגיה הנכונה
שתי אסטרטגיות branching דומינטיות ב-2026. הבחירה ביניהן משפיעה על מבנה ה-CI/CD pipeline ועל קצב ה-delivery.
**GitFlow:**
main (production) ──────────────────────────────────►
↑ merge release
develop ─────────────────────────────────►
↑ feature done ↑ hotfix merge
feature/auth ──────────►
release/1.2 ───────────►
hotfix/critical ──────────────────────►יתרונות: גרסאות מסודרות, release branches, hotfixes מנוהלים.
חסרונות: branching מורכב, merges תכופים בין branches, long-lived feature branches → merge conflicts גדולים, CI/CD מסובך.
**Trunk-Based Development (TBD):**
main (trunk) ──commit──commit──commit──commit──commit──►
↑ ↑ ↑
short-lived feature bugfix feature
branches (1-3 days) (hours) (2 days)כלל הזהב של TBD: **כל branch חי לכל היותר 1-3 ימים** ואז נמזג חזרה ל-main.
יתרונות:
- CI/CD פשוט — רק main נבנה ונפרס
- integration conflicts קטנים (branches חיים קצר)
- feature flags מאפשרות לשחרר features חלקיים
- Google, Facebook, Netflix — כולם משתמשים ב-TBD
חסרונות: דורש feature flags ל-incomplete features, דורש test coverage גבוהה כי main תמיד אמורה להיות production-ready.
Feature flags ב-TBD:
// code שנכנס ל-main אבל disabled בproduction
if (featureFlags.isEnabled('new-payment-flow', userId)) {
return newPaymentFlow();
} else {
return legacyPaymentFlow();
}**מה השוק הישראלי מצפה:**
Startups ו-SaaS → TBD בדרך כלל. Enterprise עם regulated releases → GitFlow או GitHub Flow (GitFlow מפושט). GitHub Flow (ללא develop, release branches) הוא פשרה נפוצה: main + short-lived feature branches + PR → merge → deploy.
GitHub Flow בקיצור:
# 1. Branch מ-main
git checkout -b feature/add-search main
# 2. Commits קצרים, push מוקדם
git push -u origin feature/add-search
# 3. PR → review → CI → merge → deployGit Bisect: איתור regression בדיוק של commit
git bisect הוא כלי חסום שרוב המפתחים לא מכירים, אבל הוא חוסך שעות כשצריך למצוא מתי bug נכנס ל-codebase. הוא מבצע binary search על היסטוריית ה-commits.
תרחיש: application פרוסה ב-production. גרסה 1.0 (לפני שבוע) עבדה. גרסה נוכחית שבורה. יש 200 commits ביניהם. איזה commit שבר את הכל?
בלי bisect: בדיקה ידנית של commits אחד אחד — O(n).
עם bisect: binary search — O(log n) = ~8 בדיקות ל-200 commits.
# התחל bisect session
git bisect start
# סמן את ה-commit הנוכחי כשבור
git bisect bad
# סמן commit שידוע כטוב (hash של גרסה ישנה שעבדה)
git bisect good v1.0
# או לפי hash:
git bisect good a3f2c1d
# Git בוחר commit אמצעי אוטומטית ומבצע checkout
# הרץ test ידנית:
npm test
# אם test עבר:
git bisect good
# אם test נכשל:
git bisect bad
# חזור על כן עד ש-Git מצא את ה-commit האשם:
# Bisect: first bad commit is [hash]
# Author: ...
# Date: ...
# לסיים את ה-session ולחזור ל-HEAD:
git bisect reset**Automated bisect** — אם יש script שמחזיר 0 כשהכל תקין ו-non-0 כשיש bug:
git bisect start
git bisect bad HEAD
git bisect good v1.0
# Git ירוץ את ה-script על כל commit ויחליט אוטומטית
git bisect run ./scripts/test-regression.sh
# בסוף: 'first bad commit is...'
git bisect resetדוגמה לscript בדיקה:
#!/usr/bin/env bash
# scripts/test-regression.sh
set -e
npm install --silent
npm run build --silent
curl -sf http://localhost:3000/api/health | grep -q '"status":"ok"'נקודה חשובה: אם ה-commit שנבדק לא ניתן לבדיקה (למשל build נכשל), משתמשים ב-git bisect skip ו-Git ממשיך לcommit אחר.
GitHub Actions: CI/CD Pipeline בסיסי ל-DevOps
GitHub Actions הוא ה-CI/CD המוביל בישראל בארגונים שעובדים עם GitHub. הבנת מבנה ה-YAML היא דרישת סף.
**מבנה בסיסי:**
# .github/workflows/ci.yml
name: CI Pipeline
on:
push:
branches: [main, 'feature/**']
pull_request:
branches: [main]
env:
NODE_VERSION: '22'
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run linting
run: npm run lint
- name: Run tests
run: npm test
- name: Build
run: npm run build
deploy:
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
steps:
- name: Deploy to production
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SERVER_SSH_KEY }}
script: |
cd /opt/myapp
git pull origin main
npm ci --production
pm2 restart myapp**מושגי ליבה:**
- on — trigger: push, pull_request, schedule, workflow_dispatch (ידני)
- jobs — פועלות במקביל כברירת מחדל
- needs — תלות בין jobs (deploy מחכה ל-test)
- if — תנאי הרצה
- steps — שלבים סדרתיים בתוך job
- uses — action מ-GitHub Marketplace
- run — bash command רגיל
- ${{ secrets.NAME }} — secrets מ-GitHub Settings → Secrets
- ${{ github.ref }} — context variables
**Caching dependencies:**
- uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-**Matrix builds** — בדוק על מספר Node versions בבת אחת:
strategy:
matrix:
node-version: [18, 20, 22]
steps:
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}**Docker push pipeline:**
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ghcr.io/${{ github.repository }}:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=maxProtected Branches ומדיניות PR Review
protected branches הם הכלי שמונע מ-CI/CD לפרוס קוד שלא עבר review ו-tests. הם מוגדרים ב-GitHub → Settings → Branches → Branch protection rules.
**הגדרות קריטיות:**
1. **Require a pull request before merging** — חוסם push ישיר ל-main. כל שינוי חייב לעבור PR.
2. **Require status checks to pass before merging** — ה-CI pipeline (GitHub Actions) חייב להצליח לפני ש-PR ניתן למיזוג:
Required status checks:
✓ test (ubuntu-latest)
✓ lint
✓ security-scan
3. **Require branches to be up to date before merging** — ה-PR branch חייב לכלול את כל ה-commits מ-main. מונע situations שבהן PR עבר CI אבל בינתיים נכנסו commits שגורמים לconflict.
4. **Require review from code owners** — קובץ CODEOWNERS מגדיר מי אחראי על כל חלק:
# .github/CODEOWNERS
# כל קובץ בroot — כל reviewer יכול לאשר
* @team-lead
# infrastructure — רק devops יכול לאשר
infrastructure/ @devops-team
# API routes — backend team
api/src/routes/ @backend-team
5. **Dismiss stale pull request approvals** — אם נדחף commit חדש ל-PR אחרי שאושר, האישור מבוטל ונדרש review מחדש.
6. **Require signed commits** — GPG-signed commits בלבד (מכוסה בסעיף הבא).
**Branch protection דרך CLI (GitHub CLI):**
# דרש 2 reviewers ו-CI לפני merge
gh api repos/{owner}/{repo}/branches/main/protection \
--method PUT \
--field required_status_checks='{"strict":true,"contexts":["test","lint"]}' \
--field enforce_admins=true \
--field required_pull_request_reviews='{"required_approving_review_count":2}'**CODEOWNERS patterns:**
# syntax: pattern owner1 owner2
docs/ @tech-writers
*.tf @devops-team @security-team
src/auth/ @security-teamGit Stash ו-Worktree: עבודה מקבילית ב-repository
שני כלים שפותרים בעיות אמיתיות של צוות פיתוח:
**git stash — שמירה זמנית של שינויים לא גמורים:**
תרחיש: עובדים על feature, מגיע hotfix דחוף. לא רוצים לcommit work-in-progress.
# שמור שינויים נוכחיים (כולל untracked files)
git stash push -m 'WIP: payment feature refactor' --include-untracked
# עכשיו working tree נקי — עבור לhotfix
git checkout -b hotfix/critical-bug main
# ... תקן ...
git commit -m 'fix: critical auth bypass'
git push origin hotfix/critical-bug
# חזור לעבודה הקודמת
git checkout feature/payment
git stash pop
# אם יש כמה stashes:
git stash list
# stash@{0}: WIP: payment feature refactor
# stash@{1}: ...
git stash pop stash@{0}פקודות stash שימושיות:
git stash list # רשימת כל stashes
git stash show stash@{0} # מה יש ב-stash
git stash apply stash@{0} # החל stash בלי להסיר אותו
git stash drop stash@{0} # מחק stash ספציפי
git stash clear # מחק את כל ה-stashes
git stash branch feature/new-branch stash@{0} # stash → branch חדש**git worktree — שתי copies של repo באותו זמן:**
Worktrees מאפשרות לעבוד על שני branches בו-זמנית בשני directories שונים — ללא clone נוסף.
# הוסף worktree לbranch קיים
git worktree add ../myapp-hotfix hotfix/critical-bug
# או צור branch חדש ישירות
git worktree add -b feature/new-api ../myapp-new-api main
# עכשיו:
# /home/dev/myapp → feature/payment (branch נוכחי)
# /home/dev/myapp-hotfix → hotfix/critical-bug
# /home/dev/myapp-new-api → feature/new-api
# עבוד בנפרד בכל directory:
cd ../myapp-hotfix && npm test
cd ../myapp && npm run dev
# הצג כל worktrees:
git worktree list
# הסר worktree (לא מוחק את ה-branch):
git worktree remove ../myapp-hotfixיתרון worktree על stash: IDE settings, node_modules, build artifacts — הכל נפרד לכל worktree. מושלם עבור reviewing PRs תוך כדי עבודה על feature משלך.
Signed Commits עם GPG: אימות זהות ב-Git
Signed commits מאמתים שה-commit נוצר על ידי בעל ה-GPG key — לא רק מישהו שיש לו גישה ל-repository. GitHub מציג '✓ Verified' על commits חתומים. ב-repositories עם regulated compliance (fintech, healthtech) זו לעיתים דרישה.
**הגדרה מלאה:**
# 1. ייצר GPG key (Ed25519 מומלץ)
gpg --full-generate-key
# בחר: (9) ECC (sign only), Curve 25519, 2y expiry
# הזן שם + כתובת email שמתאימה לחשבון GitHub שלך
# 2. קבל את ה-key ID
gpg --list-secret-keys --keyid-format=long
# sec ed25519/AABBCCDD11223344 2026-01-01 [SC]
# ה-ID הוא AABBCCDD11223344
# 3. ייצא public key להוספה ב-GitHub
gpg --armor --export AABBCCDD11223344
# העתק את הפלט → GitHub Settings → SSH and GPG keys → New GPG key
# 4. קנפג git לחתום עם ה-key הזה
git config --global user.signingkey AABBCCDD11223344
git config --global commit.gpgsign true
# עכשיו כל commit יחתם אוטומטית
# 5. אמת שcommit חתום
git log --show-signature -1
# gpg: Signature made ...
# gpg: Good signature from "Your Name <you@example.com>"**חתימת commits קיימים:**
# חתום על commit ספציפי בדיעבד (דורש rebase)
git rebase --exec 'git commit --amend --no-edit -S' HEAD~3**Protected branch + signed commits:**
ב-GitHub Branch Protection → 'Require signed commits' — מחייב שכל commit ב-PR יהיה חתום. combined עם CODEOWNERS, זה מספק audit trail מלא: מי approve-ד, מי commit-ד, וה-commit אמיתי.
**SSH signing כאלטרנטיבה פשוטה יותר:**
# Git 2.34+ תומך בחתימה עם SSH key (ללא GPG)
git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/id_ed25519.pub
git config --global commit.gpgsign trueב-GitHub Settings → SSH keys → יש להוסיף את ה-SSH key גם כ-'Signing key' (לא רק authentication).
תרגול מעשי
1. אתחול repository, commits וניתוח היסטוריה עם git log
צור git repository זמני, הגדר user config מקומי, בצע 4 commits עם messages שונים, ואז השתמש ב-git log עם פורמטים שונים כדי לאמת: את מספר ה-commits, שה-HEAD מצביע על הbranch הנכון, ושניתן לחלץ commit hashes ב-short format. תרגיל זה מדמה ניתוח repository לפני rebase או bisect.
2. כתיבת pre-commit hook ואימות הגדרתו
צור git repository זמני, כתוב script שמדמה pre-commit hook שבודק אם קיים AWS access key מדומה בstaged files ועוצר commit אם כן. אמת שה-hook מוגדר עם הרשאות execute ושה-logic של בדיקת ה-credentials עובד נכון: בדיקה על input תקין עוברת, ובדיקה על input עם מחרוזת בפורמט AWS key נכשלת.
3. סימולציית git bisect: זיהוי commit שבור בבינארי חיפוש
צור repository עם 7 commits שבהם commit מספר 5 'שבור' (מכיל מחרוזת 'BUG'). ממש לוגיקת bisect ידנית: חשב כמה בדיקות binary search יידרשו לאיתור ה-commit האשם מבין 7 commits, ואמת שניתן לזהות את ה-commit הבעייתי על ידי חיפוש ב-git log לפי תוכן הקובץ. תרגיל זה מדמה את הלוגיקה של git bisect בלי לדרוש checkout אינטראקטיבי.
שאלות חיבור
חבר את מה שלמדת בנושא זה לנושאים קודמים. אין תשובה אחת נכונה — חשיבה ביקורתית היא המטרה.
שאלת חיבור 1
ב-Docker למדת ש-Docker images מתוייגים ב-tags (למשל myapp:v1.2.3, myapp:latest) ושב-CI/CD pipeline נהוג לתייג image לפי git commit hash (myapp:${{ github.sha }}). כיצד git tagging ו-signed commits משלימים את image tagging ב-Docker, ומדוע פריסת image שמתוייג רק כ-'latest' מבלי לקשר אותו ל-git tag היא anti-pattern ב-production?
שאלת חיבור 2
ב-Linux Advanced למדת על Bash strict mode (set -euo pipefail) ועל trap לניקוי resources. כיצד ידע זה ישירות משפיע על כתיבת git hooks (pre-commit, pre-push) ועל GitHub Actions workflow steps, ומה קורה אם hook script לא משתמש ב-strict mode?
מוכן לבחינה?
בצע הערכה תיאורטית, תרגול CLI ושאלות חיבור כדי לסיים את הנושא.