Mert Özen Her Satırda Daha İleri
DevOps Git & GitHub — #34

GitHub CLI ile Terminalden GitHub: gh pr, gh issue, gh run, gh api ve Extension'lar

Mert Özen 15 Kas 2026 11 dk 7 görüntülenme
GitHub CLI ile Terminalden GitHub: gh pr, gh issue, gh run, gh api ve Extension'lar

GitHub CLI (gh) bir Git aracı değil, GitHub API'sine konuşan bir istemci; doğru scope'lu bir token'la pull request, issue, CI run'ları ve release'ler terminalden yönetilir. gh pr, gh issue, gh run, gh api, alias ve extension'ları, Bookstore'da PR'dan auto-merge'e iki satırlık akışı ve GH_HOST ile scope tuzaklarını anlatıyoruz.

Sürüm notu: Bu yazıdaki komutlar GitHub CLI 2.104.0 ve Git 2.56.1 ile denenmiştir. GitHub hesabı gerektiren gh komutları çalıştırılmadı ve çıktıları gösterilmiyor; gösterilen her çıktı oturum açılmamış bir kurulumdan ve komutların kendi --help metninden. GitHub özellikleri 15 Kasım 2026 itibarıyla GitHub Docs'tan alınmıştır.

Önce Tahmin Et

GitHub CLI'ı (komut adı gh) yeni kurdun ve henüz oturum açmadın. Sitedeki serilerin ortak örnek uygulaması Bookstore'un (bir online kitapçı) API repo'sundasın. Aşağıdaki üç komuttan hangileri çalışır?

$ gh --version
gh version 2.104.0 (2026-11-12)
https://github.com/cli/cli/releases/tag/v2.104.0
$ gh pr status
To get started with GitHub CLI, please run:  gh auth login
Alternatively, populate the GH_TOKEN environment variable with a GitHub API authentication token.
$ gh extension list
To get started with GitHub CLI, please run:  gh auth login
Alternatively, populate the GH_TOKEN environment variable with a GitHub API authentication token.

Yalnız ilki. Pull request durumunun hesap istemesi beklenen bir şey; yerel extension listesinin de istemesi şaşırtıcı. gh bir Git aracı değil, GitHub'ın API'sine konuşan bir istemci: neredeyse her komutu bir token ister ve onunla GitHub'a gider. Bu yazıda pull request, issue, CI ve release işlerini terminalden yürütmeyi, gh api ile API'ye doğrudan erişmeyi ve gh'yi alias ve extension'larla genişletmeyi görüyoruz. Bu laboratuvarda GitHub hesabı kullanılmadığı için hesap isteyen komutları çıktısız, açıklamayla veriyoruz.

Kurulum, Oturum ve Scope'lar

Windows'ta winget install --id GitHub.cli --source winget ile kurulur; kurulumdan sonra Windows Terminal'de yeni bir pencere açman gerekir, yeni sekme yetmez. Sonra:

# çalıştırılmadı: GitHub hesabı gerektirir
$ gh auth login
$ gh auth status
$ gh auth setup-git

gh auth login tarayıcıda bir onay kodu ile oturum açar ve token'ı sistemin credential store'una (Windows'ta Credential Manager) koyar; store yoksa düz metin bir dosyaya düşer ve gh auth status dosyanın yerini söyler. gh auth setup-git, gh'yi Git'in credential helper'ı olarak kaydeder; böylece git push da aynı token'ı kullanır. GitHub'a bağlanmak yazısında gördüğümüz credential helper ve token mantığının aynısı. Oturum açmamışken gh auth status şunu der:

$ gh auth status
You are not logged into any GitHub hosts. To log in, run: gh auth login

Token'ın yetkisi scope'larla belirlenir. gh auth login --help en az repo, read:org ve gist gerektiğini söylüyor. Bir iş yetmezse token'ı genişletirsin:

$ gh auth refresh --help | grep -E -- '--(scopes|remove-scopes)[ =]'
  -r, --remove-scopes strings           Authentication scopes to remove from gh
  -s, --scopes strings                  Additional authentication scopes for gh to have
  $ gh auth refresh --scopes write:org,read:public_key
  $ gh auth refresh --remove-scopes delete_repo

En sık karşılaşılanı workflow scope'u: .github/workflows/ altındaki bir dosyayı değiştiren push, bu scope olmadan reddedilir. Çözüm gh auth refresh -s workflow. Birden fazla hesabın varsa (git config yazısındaki Ada gibi) gh auth login ikinci hesabı da ekler, gh auth switch --user <hesap> aralarında geçiş yapar.

Pull Request'ler: create, checkout, review, merge

Bir feature branch'inde iş bitti. GitHub flow'un bütün adımları terminalden yürür:

# çalıştırılmadı: GitHub hesabı gerektirir
$ gh pr create --fill --draft
$ gh pr ready
$ gh pr checkout 42
$ gh pr review 42 --approve
$ gh pr merge 42 --squash --delete-branch

--fill, başlığı ve açıklamayı commit'lerden doldurur; commit mesajlarına özen göstermenin bir getirisi daha. Branch henüz push'lanmamışsa gh nereye push'lanacağını sorar. Komutların seçenekleri yerel yardım metninde:

$ gh pr create --help | grep -E -- '--(fill|fill-first|draft)\['
  -d, --draft[=true|false]                Mark pull request as a draft
  -f, --fill[=true|false]                 Use commit info for title and body
      --fill-first[=true|false]           Use first commit info for title and body
$ gh pr review --help | grep -E -- '--(approve|request-changes|comment)\['
  -a, --approve[=true|false]           Approve pull request
  -c, --comment[=true|false]           Comment on a pull request
  -r, --request-changes[=true|false]   Request changes on a pull request
$ gh pr merge --help | grep -E -- '--(auto|squash|delete-branch|disable-auto)'
      --auto[=true|false]            Automatically merge only after necessary requirements are met
  -d, --delete-branch[=true|false]   Delete the local and remote branch after merge
      --disable-auto[=true|false]    Disable auto-merge for this pull request
  -s, --squash[=true|false]          Squash the commits into one commit and merge it into the base branch

gh pr checkout 42, bir ekip arkadaşının pull request'ini (PR) yerelde denemenin en kısa yolu: branch'i fetch'ler, yerelde oluşturur ve ona geçer; fork'tan gelen PR'larda da çalışır. Merge yöntemleri pull request yazısında gördüğümüz üç yöntemin aynısı: --merge, --squash, --rebase.

Issue'lar ve Issue Template'leri

Issue'lar için de aynı fikir: gh issue create, gh issue list --label bug, gh issue view 17 --comments, gh issue close 17. Bookstore'da hata kayıtlarının hep aynı bilgiyle gelmesi için repo'da bir issue form'u var, .github/ISSUE_TEMPLATE/bug.yml:

name: Bug report
description: Something in Bookstore does not work as expected
labels: [bug]
body:
  - type: input
    id: page
    attributes:
      label: Page or endpoint
      placeholder: /api/orders
    validations:
      required: true
  - type: textarea
    id: steps
    attributes:
      label: Steps to reproduce
    validations:
      required: true

GitHub'ın web arayüzü bu dosyayı bir form olarak gösterir. Terminalde --template ile şablonu seçebilirsin; --title ve --body vermezsen gh eksikleri sorar:

$ gh issue create --help | grep -E -- '--(template|label|title|body) '
  -b, --body string           Supply a body. Will prompt for one otherwise.
  -l, --label name            Add labels by name
  -T, --template name         Template name to use as starting body text
  -t, --title string          Supply a title. Will prompt for one otherwise.
  $ gh issue create --title "I found a bug" --body "Nothing works"
  $ gh issue create --label "bug,help wanted"
  $ gh issue create --label bug --label "help wanted"
  $ gh issue create --template "Bug Report"

CI'ı Terminalden İzlemek ve Release Çıkarmak

PR'ı açtıktan sonra tarayıcıda Actions sekmesini yenilemek yerine:

# çalıştırılmadı: GitHub hesabı gerektirir
$ gh run list --branch feature/institutional-discount --limit 5
$ gh run watch --exit-status
$ gh run view --log-failed
$ gh run rerun --failed
$ gh workflow run ci.yml -f configuration=Debug
$ gh release create v1.2.0 --verify-tag --generate-notes

run watch çalışan bir run'ı adım adım canlı gösterir; --exit-status ile run başarısız olursa kendisi de sıfırdan farklı kodla çıkar, yani bir script'te koşul olarak kullanılabilir. run view --log-failed yalnız başarısız adımların log'unu getirir. rerun --failed yalnız düşen job'ları (ve bağlı oldukları job'ları) yeniden çalıştırır; ara sıra düşen bir test için bütün matrix'i baştan koşturmaktan ucuzdur. --debug, GitHub Actions serisinin debug, hız ve maliyet yazısında gördüğümüz debug logging'i o yeniden çalıştırma için açar:

$ gh run rerun --help | grep -E -- '--(failed|debug|job)'
Note that due to historical reasons, the `--job` flag may not take what you expect.
However, this `<number>` should not be used with the `--job` flag and will result in the
  -d, --debug[=true|false]    Rerun with debug logging
      --failed[=true|false]   Rerun only failed jobs, including dependencies
  -j, --job string            Rerun a specific job ID from a run, including dependencies

gh workflow run, workflow_dispatch trigger'ı olan bir workflow'u başlatır ve input'ları -f ile verir. Bookstore'un CI'ında bu trigger ve bir configuration input'u var; GitHub Actions serisinin trigger'lar yazısında workflow_dispatch'i ayrıntılı gördük:

on:
  pull_request:
  workflow_dispatch:
    inputs:
      configuration:
        description: Build configuration
        type: choice
        options: [Release, Debug]
        default: Release

Dosyanın tamamını actionlint ile kontrol ettik:

$ actionlint -version | head -1
1.7.12
$ actionlint .github/workflows/ci.yml && echo actionlint-ok
actionlint-ok

gh release create v1.2.0 --verify-tag --generate-notes, tag ve release yazısındaki işin GitHub tarafını yapar: --verify-tag tag remote'ta yoksa durur (tag'i önce kendin push'lamış olmalısın), --generate-notes notları birleşmiş PR'lardan GitHub'ın Release Notes API'siyle üretir.

gh api: REST ve GraphQL

gh'nin hazır komutu olmayan her şey için gh api var. Kimlik doğrulamayı, host'u ve sayfalamayı halleder; geri kalanı GitHub'ın REST (kaynak odaklı HTTP API stili) API'si, yani v3, ya da GraphQL API'si, yani v4:

# çalıştırılmadı: GitHub hesabı gerektirir
$ gh api repos/{owner}/{repo}/pulls --jq '.[] | "\(.number) \(.title)"'
$ gh api --paginate repos/{owner}/{repo}/issues -f state=closed --method GET --jq '.[].number'
$ gh api graphql -f query='query { viewer { login } }' --jq '.data.viewer.login'

{owner} ve {repo} bulunduğun repo'nun remote'undan doldurulur. --jq, yanıtı ayrı bir jq kurulumuna gerek olmadan filtreler. -f ve -F parametre ekler ve parametre eklenince istek varsayılan olarak POST'a döner; listeleme için --method GET yazmayı unutma. --paginate bütün sayfaları sırayla çeker. Seçeneklerin yerel yardımdaki karşılığı:

$ gh api --help | grep -E -- '--(jq|paginate|method|field|raw-field|hostname|slurp) '
`--method GET`.
  -F, --field key=value                       Add a typed parameter in key=value format (use "@<path>" or "@-" to read value from file or stdin)
      --hostname string                       The GitHub hostname for the request (default "github.com")
  -q, --jq string                             Query to select values from the response using jq syntax
  -X, --method string                         The HTTP method for the request (default "GET")
  -f, --raw-field key=value                   Add a string parameter in key=value format
  $ gh api repos/{owner}/{repo}/issues --jq '.[].title'
  $ gh api graphql --paginate -f query='
  $ gh api graphql --paginate --slurp -f query='

GitHub Actions içinde gh kullanacaksan oturum açmazsın; workflow'un token'ını GH_TOKEN: ${{ github.token }} olarak ortam değişkenine verirsin ve job'ın permissions: bölümü neye izin veriyorsa gh o kadarını yapabilir.

Alias ve Extension

Sık yazdığın komutları kısaltmak için gh alias:

$ gh alias set prs 'pr list --author "@me" --state open'
$ gh alias set --shell co-latest 'gh pr list --limit 1 --json number --jq ".[0].number" | xargs gh pr checkout'
$ gh alias list
co: pr checkout
co-latest: '!gh pr list --limit 1 --json number --jq ".[0].number" | xargs gh pr checkout'
prs: pr list --author "@me" --state open

Alias'lar yerel config'e yazılır ve hesap gerektirmez. co hazır gelen tek alias. --shell ile tanımlanan alias bir shell komutu olarak çalışır, pipe ve başka programlar kullanabilir; listede başına ! konmuş olarak görünür.

Extension'lar gh'ye yeni komut ekleyen ayrı repo'lardır: adı gh- ile başlar, gh extension install owner/gh-name ile kurulur, gh name olarak çalışır. Yardım metni burada açık bir uyarı yapıyor: extension'lar GitHub tarafından doğrulanmıyor, imzalanmıyor ya da onaylanmıyor; kurduğunda yayıncısına güvenmiş oluyorsun. Bir extension kurmadan önce kaynağına ve kimin yayınladığına bak, tıpkı bir npm ya da NuGet paketine bakacağın gibi.

Bookstore: PR'dan Auto-merge'e İki Satır

Emre'nin kurumsal indirim düzeltmesi hazır. Bütün akış şu:

# çalıştırılmadı: GitHub hesabı gerektirir
$ gh pr create --fill --base main
$ gh pr checks --watch --fail-fast && gh pr merge --squash --auto --delete-branch

İlk satır PR'ı commit'lerden açar. İkinci satır CI'ı izler, ilk başarısız check'te durur; her şey yeşilse PR'a auto-merge koyar. --auto, gerekli review'lar ve check'ler tamamlandığında GitHub'ın PR'ı kendisinin merge'lemesini sağlar; Emre terminali kapatıp başka işe geçebilir. Bunun için iki ön koşul var: repo ayarlarında "Allow auto-merge" açık olmalı ve main üzerinde zorunlu review ya da check isteyen bir kural (branch protection ya da ruleset) bulunmalı. Seçenek yalnızca hemen merge'lenemeyen PR'larda çıkar. Yazma yetkisi olmayan biri branch'e yeni commit push'larsa auto-merge kendiliğinden kapanır.

Sık Düşülen Tuzaklar

İlki token scope'larının yetmemesi. Hata mesajı çoğu zaman "resource not accessible" ya da bir push reddi olarak gelir ve sorunun scope olduğunu söylemez. Workflow dosyası push'luyorsan workflow, organizasyon bilgisi okuyorsan read:org gerekir; gh auth status token'ın scope'larını listeler, gh auth refresh -s ekler. Fine-grained token kullanıyorsan gh auth login --help onu --with-token yerine GH_TOKEN ile vermeni öneriyor.

İkincisi GitHub Enterprise Server'da GH_HOST'u unutmak. gh bir Git repo'sunun içindeyken host'u remote'tan anlar. Repo dışında, örneğin gh api ya da gh repo clone owner/name çalıştırırken varsayılan github.com'dur ve komut yanlış yere gider. Kendi sunucusu olan şirketlerde GH_HOST ortam değişkeni ya da --hostname gerekir; o host için ayrı oturum açılır. Ayarlanmış ama oturum açılmamış bir host'ta gh auth status bunu açıkça gösterir:

$ GH_HOST=github.bookstore.example gh auth status
github.bookstore.example
  X Failed to log in to github.bookstore.example using token (default)
...
  - Error: Get "https://github.bookstore.example/api/v3/": dial tcp: lookup github.bookstore.example: no such host

CI'da bu host'un token'ı GH_TOKEN değil GH_ENTERPRISE_TOKEN ile verilir; gh help environment hangi değişkenin hangi host için okunduğunu açıklıyor.

Üçüncüsü --auto'yu "check'ler geçince merge'le" sanıp ön koşulları kurmamak. Repo'da auto-merge kapalıysa ya da hiç zorunlu check yoksa seçenek işe yaramaz; PR açık kalır ve kimse fark etmez.

Dördüncüsü gh api'de parametre verince isteğin POST'a dönmesi. Listeleme yapmak isterken yanlışlıkla bir kaynak oluşturmamak için okuma isteklerinde --method GET'i açıkça yaz.

Küçük Bir Deneme

★☆☆ gh alias set ile kendi sık kullandığın iki komut için alias tanımla ve gh alias list ile gör. Ardından gh help environment çıktısında GH_HOST, GH_REPO ve GH_TOKEN'ın ne yaptığını oku.

★★☆ Kendi hesabınla bir deneme repo'su aç, workflow_dispatch input'u olan bir workflow ekle, gh workflow run ile başlat ve gh run watch --exit-status ile izle. Bir adımı bilerek düşür ve gh run view --log-failed ile gh run rerun --failed'ı dene.

★★★ gh api ile bir repo'nun son kapanan on issue'sunu --paginate ve --jq kullanarak listele, sonra aynı bilgiyi gh api graphql ile çek. İki yanıtın boyutunu ve kaç istek attığını karşılaştır.

Bu yazıyla serinin sonuna geldik: ilk commit'ten Git 3.0 hazırlığına, oradan da submodule, büyük repo'lar, config, geçmişte arama ve GitHub CLI'a kadar. Git durmuyor; seri yeni Git sürümleriyle devam edecek. 2.98 çıktığında, 3.0 geldiğinde ve sonrasında "eskiden böyle yapıyorduk, şimdi şöyle" diye yeni yazılar ekleyeceğiz.