كيفية التكرار من خلال جميع فروع GIT باستخدام نص Bash
سؤال
كيف يمكنني التكرار من خلال جميع الفروع المحلية في مستودعي باستخدام نص Bash. أحتاج إلى التكرار والتحقق من أن هناك أي فرق بين الفرع وبعض الفروع البعيدة. السابق
for branch in $(git branch);
do
git log --oneline $branch ^remotes/origin/master;
done
أحتاج إلى القيام بشيء مثل المعطى أعلاه ، لكن المشكلة التي أواجهها هي $ (GIT Branch) تعطيني المجلدات الموجودة داخل مجلد المستودع مع الفروع الموجودة في المستودع.
هل هذه هي الطريقة الصحيحة لحل هذه المشكلة؟ أم أن هناك طريقة أخرى للقيام بذلك؟
شكرًا لك
المحلول
يجب ألا تستخدم فرع git عند كتابة البرامج النصية. GIT يوفر أ واجهة "السباكة" تم تصميم هذا بشكل صريح للاستخدام في البرمجة النصية (العديد من التطبيقات الحالية والتاريخية لأوامر GIT العادية (إضافة ، الخروج ، دمج ، إلخ) ، استخدم هذه الواجهة نفسها).
أمر السباكة الذي تريده git for-each-ref:
git for-each-ref --shell \
--format='git log --oneline %(refname) ^origin/master' \
refs/heads/
ملاحظة: لا تحتاج إلى remotes/
بادئة على المرجع البعيد ما لم يكن لديك الحكام الأخرى التي تسبب origin/master
لمطابقة أماكن متعددة في مسار البحث عن اسم المرجع (انظر "اسم المرجع الرمزي ..." في قسم المراجعات المحدد في GIT-REV-PARSE (1)). إذا كنت تحاول أن تتجنب الغموض ، فانتقل مع اسم المرجع الكامل: refs/remotes/origin/master
.
سوف تحصل على الإخراج مثل هذا:
git log --oneline 'refs/heads/master' ^origin/master
git log --oneline 'refs/heads/other' ^origin/master
git log --oneline 'refs/heads/pu' ^origin/master
يمكنك أن تنشر هذا الإخراج في ش.
إذا كنت لا تحب فكرة توليد رمز shell ، فيمكنك التخلي عن القليل من المتانة* وافعل هذا:
for branch in $(git for-each-ref --format='%(refname)' refs/heads/); do
git log --oneline "$branch" ^origin/master
done
* يجب أن تكون أسماء المرجع في مأمن من تقسيم كلمة الصدفة (انظر git-check-ref-format (1)). أنا شخصياً سألتزم بالإصدار السابق (رمز الصدفة المولد) ؛ أنا أكثر ثقة من أنه لا شيء غير مناسب يمكن أن يحدث معها.
منذ أن حددت سحق وهو يدعم المصفوفات ، يمكنك الحفاظ على السلامة وتجنب توليد شجاعة حلقتك:
branches=()
eval "$(git for-each-ref --shell --format='branches+=(%(refname))' refs/heads/)"
for branch in "${branches[@]}"; do
# …
done
يمكنك أن تفعل شيئًا مشابهًا مع $@
إذا كنت لا تستخدم قذيفة تدعم المصفوفات (set --
لتهيئة و set -- "$@" %(refname)
لإضافة عناصر).
نصائح أخرى
هذا بسبب git branch
يصادف الفرع الحالي مع النجمة ، على سبيل المثال:
$ git branch
* master
mybranch
$
لذا $(git branch)
يتوسع إلى مثل * master mybranch
, ثم *
يتوسع إلى قائمة الملفات في الدليل الحالي.
لا أرى خيارًا واضحًا لعدم طباعة العلامة النجمية في المقام الأول ؛ لكن يمكنك تقطيعه:
$(git branch | cut -c 3-)
باش بنيت ، mapfile
, ، تم تصميمه لهذا
جميع فروع Git: git branch --all --format='%(refname:short)'
جميع فروع GIT المحلية: git branch --format='%(refname:short)'
جميع فروع GIT عن بُعد: git branch --remotes --format='%(refname:short)'
تكرار من خلال جميع فروع Git: mapfile -t -C my_callback -c 1 < <( get_branches )
مثال:
my_callback () {
INDEX=${1}
BRANCH=${2}
echo "${INDEX} ${BRANCH}"
}
get_branches () {
git branch --all --format='%(refname:short)'
}
# mapfile -t -C my_callback -c 1 BRANCHES < <( get_branches ) # if you want the branches that were sent to mapfile in a new array as well
# echo "${BRANCHES[@]}"
mapfile -t -C my_callback -c 1 < <( get_branches )
لموقف OP الخاص:
#!/usr/bin/env bash
_map () {
ARRAY=${1?}
CALLBACK=${2?}
mapfile -t -C "${CALLBACK}" -c 1 <<< "${ARRAY[@]}"
}
get_history_differences () {
REF1=${1?}
REF2=${2?}
shift
shift
git log --oneline "${REF1}" ^"${REF2}" "${@}"
}
has_different_history () {
REF1=${1?}
REF2=${2?}
HIST_DIFF=$( get_history_differences "${REF1}" "${REF2}" )
return $( test -n "${HIST_DIFF}" )
}
print_different_branches () {
read -r -a ARGS <<< "${@}"
LOCAL=${ARGS[-1]?}
for REMOTE in "${SOME_REMOTE_BRANCHES[@]}"; do
if has_different_history "${LOCAL}" "${REMOTE}"; then
# { echo; echo; get_history_differences "${LOCAL}" "${REMOTE}" --color=always; } # show differences
echo local branch "${LOCAL}" is different than remote branch "${REMOTE}";
fi
done
}
get_local_branches () {
git branch --format='%(refname:short)'
}
get_different_branches () {
_map "$( get_local_branches )" print_different_branches
}
# read -r -a SOME_REMOTE_BRANCHES <<< "${@}" # use this instead for command line input
declare -a SOME_REMOTE_BRANCHES
SOME_REMOTE_BRANCHES=( origin/master remotes/origin/another-branch another-remote/another-interesting-branch )
DIFFERENT_BRANCHES=$( get_different_branches )
echo "${DIFFERENT_BRANCHES}"
أنا أكرر كما هو على سبيل المثال:
for BRANCH in `git branch --list|sed 's/\*//g'`;
do
git checkout $BRANCH
git fetch
git branch --set-upstream-to=origin/$BRANCH $BRANCH
done
git checkout master;
أود أن أقترح$(git branch|grep -o "[0-9A-Za-z]\+")
إذا تم تسمية فروعك المحلية بأرقام ، و/أو أحرف AZ و/أو AZ فقط
الإجابة المقبولة صحيحة ويجب أن تكون النهج المستخدم حقًا ، ولكن حل المشكلة في باش هو تمرين رائع في فهم كيفية عمل القذائف. تتمثل الحيلة في القيام بذلك باستخدام Bash دون إجراء معالجة نصية إضافية ، وهي ضمان عدم توسيع إخراج فرع GIT كجزء من أمر يتم تنفيذه بواسطة shell. هذا يمنع النجمة من التوسع في توسيع اسم الملف (الخطوة 8) من توسيع شل (انظر http://tldp.org/ldp/bash-beginners-guide/html/sect_03_04.html)
استخدم باش في حين بناء مع أمر قراءة لتقطيع إخراج فرع GIT في خطوط. سيتم قراءة "*" كشخصية حرفية. استخدم بيان حالة لمطابقةه ، مع إيلاء اهتمام خاص لأنماط المطابقة.
git branch | while read line ; do
case $line in
\*\ *) branch=${line#\*\ } ;; # match the current branch
*) branch=$line ;; # match all the other branches
esac
git log --oneline $branch ^remotes/origin/master
done
العلامات النجمية في كل من باش قضية يجب الهروب من البناء وفي الاستبدال المعلمة مع الانزلاق الخلفي لمنع قذيفة تفسيرها على أنها شخصيات مطابقة للأنماط. كما يتم هروب المساحات (لمنع الرمز المميز) لأنك تتطابق حرفيًا "*".
أسهل خيار للتذكر في رأيي:
git branch | grep "[^* ]+" -Eo
انتاج:
bamboo
develop
master
يقيد خيار GREP's -O (-المطابقة فقط) الإخراج على الأجزاء المطابقة فقط من الإدخال.
نظرًا لأن المساحة ولا * صالحة في أسماء فروع GIT ، فإن هذا يعيد قائمة الفروع بدون الأحرف الإضافية.
تحرير: إذا كنت في حالة "رأس منفصل", ، ستحتاج إلى تصفية الإدخال الحالي:
git branch --list | grep -v "HEAD detached" | grep "[^* ]+" -oE
ما انتهى بي الأمر ، تطبيقه على سؤالك (واستلهم من CCPIZZA ذكر tr
):
git branch | tr -d ' *' | while IFS='' read -r line; do git log --oneline "$line" ^remotes/origin/master; done
(أنا أستخدم بينما الحلقات كثيرًا. بينما بالنسبة لأشياء معينة ، فأنت بالتأكيد تريد استخدام اسم متغير مدبب ["فرع" ، على سبيل المثال] ، في معظم الوقت أنا مهتم فقط بفعل شيء ما مع كل منهما خط من المدخلات. استخدام "الخط" هنا بدلاً من "الفرع" هو إشارة إلى قابلية إعادة الاستخدام/ذاكرة/كفاءة العضلات.)
تمتد من إجابة Finn (شكرًا لك!) ، سيتيح لك ما يلي التكرار على الفروع دون إنشاء برنامج نصي Shell المتداخل. إنه قوي بما فيه الكفاية ، طالما لم يكن هناك خطوط جديدة في اسم الفرع :)
git for-each-ref --format='%(refname)' refs/heads | while read x ; do echo === $x === ; done
تعمل الحلقة في Subshell ، وهو أمر جيد عادةً ما لم تقم بتعيين متغيرات Shell التي تريد الوصول إليها في القشرة الحالية. في هذه الحالة ، تستخدم استبدال العملية لعكس الأنبوب:
while read x ; do echo === $x === ; done < <( git for-each-ref --format='%(refname)' refs/heads )
إذا كنت في هذه الحالة:
git branch -a
* master
remotes/origin/HEAD -> origin/master
remotes/origin/branch1
remotes/origin/branch2
remotes/origin/branch3
remotes/origin/master
وتدير هذا الرمز:
git branch -a | grep remotes/origin/*
for BRANCH in `git branch -a | grep remotes/origin/*` ;
do
A="$(cut -d'/' -f3 <<<"$BRANCH")"
echo $A
done
ستحصل على هذه النتيجة:
branch1
branch2
branch3
master
أبقيها بسيطة
الطريقة البسيطة للحصول على اسم الفرع في حلقة باستخدام نص Bash.
#!/bin/bash
for branch in $(git for-each-ref --format='%(refname)' refs/heads/); do
echo "${branch/'refs/heads/'/''}"
done
انتاج:
master
other
إجابة Googlian ، ولكن دون استخدام إلى عن على
git for-each-ref --format='%(refname:lstrip=-1)' refs/heads/
for branch in "$(git for-each-ref --format='%(refname:short)' refs/heads)"; do
...
done
هذا يستخدم Git السباكة الأوامر ، والتي تم تصميمها للبرمجة النصية. إنها أيضًا بسيطة ومعتادة.
المرجعي: الانتهاء من باش جيت