Cách lặp qua tất cả các nhánh git bằng tập lệnh bash


105

Làm cách nào để tôi có thể lặp qua tất cả các nhánh cục bộ trong kho lưu trữ của mình bằng cách sử dụng tập lệnh bash. Tôi cần lặp lại và kiểm tra xem có sự khác biệt nào giữa chi nhánh và một số chi nhánh ở xa không. Ví dụ

for branch in $(git branch); 
do
    git log --oneline $branch ^remotes/origin/master;
done

Tôi cần làm điều gì đó như đã nêu ở trên, nhưng vấn đề tôi đang gặp phải là $ (git branch) cung cấp cho tôi các thư mục bên trong thư mục kho lưu trữ cùng với các nhánh có trong kho lưu trữ.

Đây có phải là cách chính xác để giải quyết vấn đề này? Hoặc là có một cách khác để làm điều đó?

Cảm ơn bạn



1
@pihentagy Câu hỏi này được viết trước câu hỏi được liên kết.
kodaman

Vd: -for b in "$(git branch)"; do git branch -D $b; done
Abhi

Câu trả lời:


156

Bạn không nên sử dụng git branch khi viết script. Git cung cấp giao diện "hệ thống ống nước" được thiết kế rõ ràng để sử dụng trong kịch bản (nhiều triển khai hiện tại và lịch sử của các lệnh Git thông thường (thêm, kiểm tra, hợp nhất, v.v.) sử dụng cùng giao diện này).

Lệnh sửa ống nước bạn muốn là git for-each-ref :

git for-each-ref --shell \
  --format='git log --oneline %(refname) ^origin/master' \
  refs/heads/

Lưu ý: Bạn không cần remotes/tiền tố trên tham chiếu từ xa trừ khi bạn có các tham chiếu khác khiến origin/masterkhớp với nhiều vị trí trong đường dẫn tìm kiếm tên tham chiếu (xem “Tên tham chiếu tượng trưng.…” Trong phần Chỉ định bản sửa đổi của git-rev-parse (1) ). Nếu bạn đang cố gắng explictly tránh sự mơ hồ, sau đó đi với tên ref đầy đủ: refs/remotes/origin/master.

Bạn sẽ nhận được đầu ra như thế này:

git log --oneline 'refs/heads/master' ^origin/master
git log --oneline 'refs/heads/other' ^origin/master
git log --oneline 'refs/heads/pu' ^origin/master

Bạn có thể chuyển đầu ra này thành sh .

Nếu bạn không thích ý tưởng tạo mã shell, bạn có thể từ bỏ một chút tính mạnh mẽ * và làm điều này:

for branch in $(git for-each-ref --format='%(refname)' refs/heads/); do
    git log --oneline "$branch" ^origin/master
done

* Tên tham chiếu phải an toàn khỏi sự tách từ của trình bao (xem định dạng git-check-ref (1) ). Cá nhân tôi sẽ gắn bó với phiên bản cũ (mã shell được tạo); Tôi tự tin hơn rằng không có gì không phù hợp có thể xảy ra với nó.

Vì bạn đã chỉ định bash và nó hỗ trợ các mảng, bạn có thể duy trì sự an toàn và vẫn tránh tạo ra các lỗi của vòng lặp của bạn:

branches=()
eval "$(git for-each-ref --shell --format='branches+=(%(refname))' refs/heads/)"
for branch in "${branches[@]}"; do
    # …
done

Bạn có thể làm điều gì đó tương tự với $@nếu bạn không sử dụng trình bao hỗ trợ mảng ( set --để khởi tạo và set -- "$@" %(refname)thêm phần tử).


39
Nghiêm túc. Không có cách nào đơn giản hơn để làm điều này?
Jim Fell

4
Nhưng nếu tôi muốn sử dụng một trong các tùy chọn lọc của nhánh git, chẳng hạn như --merged, tôi có phải sao chép logic trong nhánh git không? Phải có một cách tốt hơn để làm điều này.
Thayne

3
Phiên bản đơn giản hơn:git for-each-ref refs/heads | cut -d/ -f3-
wid

4
@wid: Hoặc đơn giản làgit for-each-ref refs/heads --format='%(refname)'
John Gietzen

5
@Thayne: câu hỏi này đã cũ, nhưng những người Git cuối cùng đã giải quyết được vấn đề: for-each-refhiện hỗ trợ tất cả các bộ chọn nhánh như --mergedgit branchgit taghiện đang thực sự được triển khai về mặt git for-each-refchính nó, ít nhất là đối với các trường hợp hiện có trong danh sách. (Tạo các nhánh và thẻ mới không phải, và không nên, là một phần của for-each-ref.)
torek

50

Điều này là do git branchđánh dấu nhánh hiện tại bằng dấu hoa thị, ví dụ:

$ git branch
* master
  mybranch
$ 

do đó $(git branch)mở rộng đến vd * master mybranch, và sau đó *mở rộng đến danh sách các tệp trong thư mục hiện tại.

Tôi không thấy một tùy chọn rõ ràng nào để không in dấu hoa thị ngay từ đầu; nhưng bạn có thể cắt nó ra:

$(git branch | cut -c 3-)

4
Nếu bạn đặt trong dấu ngoặc kép, bạn có thể ngừng mở rộng dấu hoa thị - mặc dù bạn vẫn muốn xóa nó khỏi đầu ra. Một cách mạnh mẽ hơn để xóa dấu hoa thị khỏi bất kỳ điểm nào sẽ là $(git branch | sed -e s/\\*//g).
Nick

2
tốt, tôi thực sự thích 3-giải pháp của bạn .
Andrei-Niculae Petre

1
Phiên bản sed đơn giản hơn một chút:$(git branch | sed 's/^..//')
jdg

5
phiên bản tr đơn giản hơn một chút:$(git branch | tr -d " *")
ccpizza

13

Nội trang bash mapfile, được xây dựng cho việc này

tất cả các nhánh git: git branch --all --format='%(refname:short)'

tất cả các chi nhánh git địa phương: git branch --format='%(refname:short)'

tất cả các chi nhánh git từ xa: git branch --remotes --format='%(refname:short)'

lặp qua tất cả các nhánh git: mapfile -t -C my_callback -c 1 < <( get_branches )

thí dụ:

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 )

đối với tình hình cụ thể của 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}"

source: Liệt kê tất cả các nhánh git cục bộ không có dấu hoa thị


5

Tôi lặp lại ví dụ như:

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;

4

Tôi sẽ đề xuất $(git branch|grep -o "[0-9A-Za-z]\+")nếu các chi nhánh địa phương của bạn chỉ được đặt tên bằng các chữ số, az và / hoặc AZ


4

Câu trả lời được chấp nhận là đúng và thực sự nên là phương pháp được sử dụng, nhưng giải quyết vấn đề trong bash là một bài tập tuyệt vời để hiểu cách hoạt động của shell. Mẹo để thực hiện việc này bằng cách sử dụng bash mà không cần thực hiện thêm thao tác văn bản, là đảm bảo đầu ra của nhánh git không bao giờ được mở rộng như một phần của lệnh được thực thi bởi shell. Điều này ngăn không cho dấu hoa thị mở rộng trong phần mở rộng tên tệp (bước 8) của phần mở rộng shell (xem http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_03_04.html )

Sử dụng bash cấu trúc while với lệnh read để cắt đầu ra của nhánh git thành các dòng. Dấu '*' sẽ được đọc dưới dạng ký tự chữ. Sử dụng một câu lệnh viết hoa để đối sánh nó, đặc biệt chú ý đến các mẫu phù hợp.

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

Các dấu hoa thị trong cả cấu trúc trường hợp cơ bản và trong thay thế tham số cần phải được thoát bằng dấu gạch chéo ngược để ngăn trình bao diễn giải chúng như các ký tự khớp với mẫu. Các dấu cách cũng được thoát ra (để ngăn chặn mã hóa) vì bạn đang khớp với '*' theo nghĩa đen.


4

Tùy chọn dễ nhớ nhất theo ý kiến ​​của tôi:

git branch | grep "[^* ]+" -Eo

Đầu ra:

bamboo
develop
master

Tùy chọn -o của Grep (- chỉ phù hợp) giới hạn đầu ra chỉ với các phần phù hợp của đầu vào.

Vì cả dấu cách và dấu * đều không hợp lệ trong tên nhánh Git, điều này trả về danh sách các nhánh không có ký tự thừa.

Chỉnh sửa: Nếu bạn đang ở trạng thái 'đầu tách rời' , bạn sẽ cần lọc ra mục nhập hiện tại:

git branch --list | grep -v "HEAD detached" | grep "[^* ]+" -oE


3

Những gì tôi đã kết thúc, áp dụng cho câu hỏi của bạn (& lấy cảm hứng từ việc đề cập đến ccpizza tr):

git branch | tr -d ' *' | while IFS='' read -r line; do git log --oneline "$line" ^remotes/origin/master; done

(Tôi sử dụng vòng lặp while rất nhiều. Mặc dù đối với những thứ cụ thể, bạn chắc chắn muốn sử dụng tên biến trỏ ["branch" chẳng hạn], hầu hết thời gian tôi chỉ quan tâm đến việc làm gì đó với mỗi dòng đầu vào. Sử dụng 'line' ở đây thay vì 'branch' là một dấu hiệu cho thấy khả năng tái sử dụng / bộ nhớ cơ bắp / hiệu quả.)


1

Mở rộng từ câu trả lời của @ finn (cảm ơn bạn!), Phần sau sẽ cho phép bạn lặp qua các nhánh mà không cần tạo tập lệnh shell can thiệp. Nó đủ mạnh, miễn là không có dòng mới trong tên chi nhánh :)

git for-each-ref --format='%(refname)' refs/heads  | while read x ; do echo === $x === ; done

Vòng lặp while chạy trong một vỏ con, điều này thường ổn trừ khi bạn đang thiết lập các biến trình bao mà bạn muốn truy cập trong trình bao hiện tại. Trong trường hợp đó, bạn sử dụng thay thế quy trình để đảo ngược đường ống:

while read x ; do echo === $x === ; done < <( git for-each-ref --format='%(refname)' refs/heads )

1

Nếu bạn đang ở trạng thái này:

git branch -a

* master

  remotes/origin/HEAD -> origin/master

  remotes/origin/branch1

  remotes/origin/branch2

  remotes/origin/branch3

  remotes/origin/master

Và bạn chạy mã này:

git branch -a | grep remotes/origin/*

for BRANCH in `git branch -a | grep remotes/origin/*` ;

do
    A="$(cut -d'/' -f3 <<<"$BRANCH")"
    echo $A

done        

Bạn sẽ nhận được kết quả này:

branch1

branch2

branch3

master

Giải pháp này có vẻ ít phức tạp hơn đối với tôi +1
Abhi

Điều này cũng làm việc cho tôi:for b in "$(git branch)"; do git branch -D $b; done
Abhi

1

Đơn giản

Cách đơn giản để lấy tên chi nhánh trong vòng lặp bằng cách sử dụng tập lệnh bash.

#!/bin/bash

for branch in $(git for-each-ref --format='%(refname)' refs/heads/); do
    echo "${branch/'refs/heads/'/''}" 
done

Đầu ra:

master
other

1

Câu trả lời của Googlian, nhưng không sử dụng cho

git for-each-ref --format='%(refname:lstrip=-1)' refs/heads/

1
Điều này không hoạt động đối với các tên chi nhánh có không gian tên, như trong các chi nhánh có dấu gạch chéo trong đó. Điều này có nghĩa là các nhánh được tạo bởi depndabot, trông giống như "depndabot / npm_and_yarn / stylescript-3.9.5", sẽ xuất hiện thay thế dưới dạng "stylescript-3.9.5".
ecbrodie

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.