注:この特定の質問は、私がこの回答を書いている時点では、かなり古いものです。これらの問題の多くを修正したバージョンのGitが最初にリリースされる3年前に投稿されました。 ただし、説明者と一緒に最新の回答を追加する価値があるようです。 既存の受け入れられた回答git fetch -p
は、実行することを示唆しています。1これは良い考えですが、最近はあまり必要とされていません。Gitバージョン1.8.2がリリースされる前に、それははるかに必要でした。そのGitは最初の質問から3年後にリリースされました。
1-p
or--prune
オプションは必須ではなく、リンクされた回答で括弧内にのみ提案されています。それが何をするかについては、以下のより長いセクションを参照してください。
これは実際にどのようにして起こりますか?
元の質問は次のとおりです。
これは実際にどのようにして起こりますか?
問題のこれは、後git push origin master
にOPが実行されgit status
、メッセージが表示されたOn branch master
後Your branch is ahead of 'origin/master' by 1 commit.
、質問に適切に回答するには、それを細かく分割する必要があるという事実です。
まず、各(ローカル)ブランチにはアップストリーム設定があります
この主張は実際には少し強すぎます。独自のGitリポジトリ内の各ローカルブランチは、 Gitがアップストリームを呼び出す1つの設定を持つことができます。または、そのブランチにアップストリームを含めることはできません。古いバージョンのGitは、これをアップストリーム設定と呼ぶことについてあまり一貫性がありませんでしたが、最新のGitでは、より一貫性があります。また、アップストリームを設定またはクリアするための機能もあります。git branch --set-upstream-to
git branch --unset-upstream
これらは、現在のブランチ--set-upstream-to
に--unset-upstream
影響します。現在のブランチは、あなたが言っているとき、またはそれが何を言っているときでも、あなたがいるブランチです。このブランチは、または(Git 2.23以降)<code>gitスイッチを使用して選択します。2 チェックアウトしたブランチが何であれ、それが現在のブランチです。3on
git status
on branch xyzzy
git checkout
を使用する--unset-upstream
と、現在のブランチのアップストリームが削除されます。アップストリームがない場合、これにより、先行または遅延、または分岐に関するメッセージが停止します。ただし、このメッセージは役立つことを目的としているため、アップストリームを削除して、発生を停止させるべきではありません。(メッセージが役に立たない場合は、メッセージを無視してください。結局のところ、エラーではありません。)
を実行するgit branch --set-upstream-to=origin/xyzzy
と、現在のブランチのアップストリームがに設定されorigin/xyzzy
ます。という名前のブランチの場合xyzzy
、これが一般的な正しい設定になります。ブランチを作成する動作には、(通常は正しい)アップストリームが自動的に設定されるものと、そうでないものがあります。したがって、正しいアップストリームを自動的に設定するブランチ作成操作を使用した場合は、何もする必要はありません。別のアップストリームが必要な場合、またはアップストリームを設定しないブランチ作成操作を使用した場合は、これを使用してアップストリームを変更できます。
アップストリームに設定できるものは次のとおりです。
- 別の独自の(ローカル)ブランチ:現在のブランチの上流に
git branch --set-upstream-to=experiment
独自のローカルを作成します。experiment
また
- またはなどのリモートトラッキング名のいずれか。これらは、によって出力される名前です。Gitはこれらのリモートトラッキングブランチ名を呼び出します(ここに「ブランチ」という単語をドロップするのが好きです)。これについては後で詳しく説明します。
origin/main
origin/master
origin/xyzzy
git branch -r
印刷される前、後ろ、最新、または分岐したメッセージgit status
は、やや魔法のように見えるコマンドを実行することから派生します。
git rev-list --count --left-right $branch...$upstream
ここ$branch
で、は現在のブランチ名であり、$upstream
はそのアップストリーム設定(git branch --set-upstream-to
上から)からの文字列です。ここでは、2つの名前の間に3つのドットがあり--count
、2つの数字を吐き出すには、、、、--left-right
および3つのドットがすべて必要です。git rev-list
2 Git 2.23以降を使用している場合は、これまで初心者をトラブルに巻き込んだ(場合によってはGitの専門家をつまずかせた)git switch
トリッキーな動作を回避できるため、移行することをお勧めします。git checkout
ただし、慣れている場合はgit checkout
、引き続きサポートされているので、好きなだけ使い続けることができます。本当の問題は、基本的にそれgit checkout
が過度に強力であり、予期せずに作業を破壊する可能性があることです。新しいgit switch
ものは意図的にそれほど強力ではなく、それを行いません。「意図的に自分の作業を破棄する」操作はに移動されましgit restore
た。
3 GitがデタッチドHEADモードと呼んでいる場合、ブランチがない可能性があります。使用すると、突然このモードになる可能性があります(ただし、大きな恐ろしい警告が出力されるため、恐ろしい警告が表示されない場合は表示されません)が、使用する場合は、分離を許可する必要があります-HEADを使用したモード。このモードには何の問題もありません。新しいコミットを失わないように、このモードに入ったら注意する必要があります。注意しないと簡単に紛失してしまいます。通常モードでは、Gitはこのような新しいコミットを失うことはありません。git checkout
git switch
git switch --detach
デタッチドHEADモードの場合、ブランチがないため、定義上、アップストリームはありません。また、この質問の内容はいずれも当てはまりません。
到達可能性
この部分は少し技術的であり、その多くをWebサイトThink Like(a)Gitにアウトソーシングします。ここでは、次のように要約します。ブランチ名(main
またはxyzzy
)とリモートトラッキング名(origin/main
、origin/xyzzy
)は、Gitがコミットを見つける方法です。Gitはすべてコミットに関するものです。ブランチ名は、コミットを見つけるためにのみ重要です。もちろん、それらが見つからない場合は問題が発生しているため、ブランチ名は重要です。しかし、重要なのは到達可能性です。これは専門用語です。
Gitリポジトリ内の各コミットには番号が付けられ、文字と数字の大きな醜い16進文字列が使用されます。これはコミットのハッシュIDであり、Gitが実際にコミットを見つける方法です。
各コミットには2つのものが含まれます:すべてのソースファイルの完全なスナップショット(特別な、圧縮された、Git化された、および重複排除された形式)、およびコミット自体に関するいくつかの情報:誰が、いつ、そしてなぜそれを作成したかを示すメタデータ(たとえば、ログメッセージ)。メタデータでは、各コミットは以前のコミットのコミット番号を保持します。これは、あるコミットが別の(以前の)コミットを見つけることができることを意味します。
Think Like(a)Gitが指摘しているように、これは鉄道列車に少し似ています。電車に乗ったら、それは素晴らしいことです。この場合、以前のすべての電車の停車駅まで自動的に後方に移動します。しかし、最初にあなたは駅への道を見つけなければなりません。Gitブランチ名はそれを行います:それはあなたのブランチの最新のコミットのハッシュIDを保持します。
このように描くことができます:
... <-F <-G <-H <--branch
ブランチ名は、最新のコミットbranch
のハッシュIDを保持します。名前はコミットを指していると言います。実際の大きな醜いハッシュIDが何であれ、ここではその代わりに文字を使用しました。H
H
は実際のコミットであるため、保存されたスナップショット(ファイル)といくつかのメタデータがあります。メタデータでは、Gitは以前のコミットのハッシュIDを保存しました。これを以前のコミットと呼びますG
。H
それはを指して いると言いG
ます。Gitはブランチ名ポインターで見つけることができH
、メタデータを含むコミットへのアクセスをGitに与えるため、Gitは以前のコミットのハッシュIDを持ちますG
。
G
もちろん、実際のコミットでもあります。保存されたスナップショットといくつかのメタデータがあります。のメタデータでG
、Gitは以前のコミットのハッシュIDを保存しましたF
。G
これはを指していると言いますが、Gitはこの保存されたハッシュIDを使用してF
見つけることができます。F
これは、最初のコミットに到達するまで、永遠に繰り返されます。そのコミット(おそらくここではそれを呼びA
ます)は、以前のコミットがないため、以前のコミットを逆方向に指すことはありません。
この到達可能性の概念は、基本的に、H
ブランチ名branch
で検出されたコミットで開始し、逆方向に作業した場合に何が起こるかをまとめたものです。commitH
に到達し、commitに到達し、commitにG
到達し、に到達F
します。
ブランチ名とリモートトラッキング名
先ほど述べたように、ブランチ名はコミットの生のハッシュIDを保持します。これにより、Gitはそのコミットを見つけることができます。ただし、ブランチ名にはもう1つの特別な機能があります。
git checkout
またはgit switch
を使用してブランチにアクセスし、新しいコミットを行うと、Gitはブランチ名に保存されているハッシュIDを自動的に更新します。つまり、次のような一連のコミットがあるとします。
...--F--G--H <-- xyzzy (HEAD)
私たちは「オン」ブランチxyzzy
にいます。これは、特別な名前を付けることで示したいと思いHEAD
ます。これは、図に複数のブランチ名がある場合に役立ちます。H
これは、現時点では最新のコミットであることに注意してください。しかし、今度は通常の方法で別のものを作成します。
この新しいコミットは、他のコミットと同じように、新しい、一意の、大きな醜い16進ハッシュIDを取得します。Gitは、新しいコミットがコミットを逆方向に指していることを確認します。これは、新しいコミットを作成するためH
に使用したコミットだからです。この新しいコミットを表すために文字を使用します。それを描きましょう:I
...--F--G--H <-- xyzzy (HEAD)
\
I
この写真は実際にはコミットの途中です。Gitは作成I
しましたが、git commit
アクションはまだ完了していません。この質問を自問してください:後でどのようにコミットを見つけますか?I
ハッシュIDが必要になります。ハッシュIDはどこに保存できますか?
あなたが言った場合:ブランチ名で、あなたは正しいです。実際、Gitに関する限り、正しいブランチ名は、現在「オン」になっている名前です。これはHEAD
、この図で添付したものです。だから今、の最後の部分としてgit commit
、GitはのI
ハッシュIDを名前に書き込みますxyzzy
。これによりI
、次のようにコミットすることがポイントになります。
...--F--G--H
\
I <-- xyzzy (HEAD)
そして今、図面にねじれの理由がないので、それをまっすぐにすることができます:
...--F--G--H--I <-- xyzzy (HEAD)
これがブランチ名の仕組みです。結局のところ、これは非常に単純です。一度にいくつかのことを頭に入れておく必要があります。名前はコミットを見つけます。最新のコミットを検索します。そこから、各コミットが以前のコミットを見つけるため、Gitは逆方向に機能します。
リモートトラッキング名はどうですか?ここでの秘訣は、Gitが他のGitと通信することです。 各Gitには独自のブランチ名があります。 あなたはあなたの master
またはを持っていますmain
; 彼らは彼らのものを持っています。あなたはあなたの xyzzy
ブランチを持っています、そして彼らも彼らのものを持つことができます。
あなたのGitは、いつでも、いつでもGitを呼び出して、ブランチ名について尋ねることができます。ただし、これはあまり効率的ではなく、インターネットから切断されている場合は機能しません。4 いずれにせよ、Gitはそれを行いません。代わりに、GitがGitを呼び出して、すべてのブランチ名とハッシュIDのリストを取得すると、Gitはそれらの名前とハッシュIDを取得して、リポジトリに保存します。これは、を実行すると発生しますgit fetch
。5
しかし、問題があります。それらのmain
or master
、またはそれらがxyzzy
ある場合は、必ずしもあなたのorまたは。と同じコミットを意味するわけではありません。ただし、解決策は簡単です。Gitはブランチ名を取得し、それをリモートトラッキング名に変換します。 main
master
xyzzy
origin
'smain
またはmaster
orが移動した場合は、単にorxyzzy
を実行します。おそらく。を使用します。あなたのGitは彼らのGitを呼び出します。ブランチ名をリストし、ハッシュIDをコミットします。あなたのGitは、必要に応じて、彼らから新しいコミットを取得します。彼らが持っているコミットと、あなたが持っていないコミットです。次に、Gitはブランチ名をリモートトラッキング名に変換し、リモートトラッキング名を作成または更新して、これを実行した時点でブランチ名が指している場所を記憶します。git fetch
git fetch origin
--prune
git fetch
を使用すると、ブランチ名が削除され--prune
た場合に対応します。。という名前のブランチがあったとしましょう。あなたは以前にそれを手に入れたので、あなたはあなたのリモートトラッキング名を持っています。それから彼らは削除したので、今回は...もう持っていません。がないと、Gitはこれを無視します。あなたは今死んでいるのに あなたの古いものを保ちます。を使用すると、Gitは次のように言います。ああ、これは今は死んでいるように見え、それを削除します。Git内の、ブランチ名の1つに対応しないリモート追跡名は、削除されるだけです。oldstuff
origin/oldstuff
oldstuff
--prune
origin/oldstuff
--prune
プルーンオプションはおそらく常にデフォルトであるはずでしたが、そうではなかったため、現在はできません。6 ただし、これをデフォルトに設定してfetch.prune
設定するtrue
ことはできます。
4これは、2010年に比べて2021年にはあまり一般的ではありません。Gitが最初にリリースされた2005年にはかなり一般的でした。たとえば、Linux会議への航空会社のフライトでは、どのような価格でもインターネットにアクセスできませんでした。
5どの名前をとるか、いつそれらをとるかの選択は、実際にはここでの答えの一部です。さまざまな制約がありますが、Gitでは時間の経過とともに変化し、まだ少し変化しています。ただし、詳細については説明しません。
6 Gitは通常、下位互換性を非常に重要視しています。たとえば、push.default
デフォルト設定をからmatching
に変更するには、1.xから2.0への切り替えが必要でした。simple
git rev-list
2つの数字を取得する方法
以前、私は、印刷される前、後ろ、最新、または分岐したメッセージがgit status
実行から派生していることを指摘しました。
git rev-list --count --left-right $branch...$upstream
ここで行うのgit rev-list
は、到達可能なコミットのカウントです。gitrevisionsのドキュメントで説明されている3ドットの構文は、集合論で対称差と呼ばれるものを生成します。ただし、数学以外の専門用語では、これを2つのコミット到達可能性テストを実行するものと考えることができます。これは次のように描画できます。
I--J <-- xyzzy (HEAD)
/
...--G--H
\
K <-- origin/xyzzy
ここでは、名前がそこを指しているため、commitJ
はブランチ名から到達可能です。xyzzy
コミットI
はコミットから到達可能でJ
あるため、同様にカウントされます。これはコミットに戻りH
ます—グラフからわかるように、これは少し特別です。
同時に、コミットK
はリモートトラッキング名から到達可能origin/xyzzy
です。コミットH
はから到達可能ですK
。コミットH
からバックまで、コミットG
などF
もすべて到達可能です。しかし、2つの「線路」はcommit :commitで結合し、以前のすべてのcommitは両方の名前から到達可能です。H
H
これにより、コミットはI-J
*名前からのみ到達可能であるという点で特別にxyzzy
なり、 *名前からのみ到達可能であるという点で特別になります。3ドット表記は、これらのコミットを検出します。一方の名前からのみ到達可能なコミット、またはもう一方の名前からのみ到達可能なコミットです。K
origin/xyzzy
ブランチ名を左側に、そのアップストリームを右側に配置し、3ドット表記を使用すると、この場合のこれら3つのコミットすべてが見つかります。を使用--count
すると、次の数値が出力されます。3 git rev-list
.使用すると、よりスマートになります。ただし、左側の名前(現在のブランチ名)のためにカウントされているコミットの数と、右側の名前(右側の名前)のためにカウントされているコミットの数をカウントする必要があります。上流の。したがって、両方のオプションと3つのドットを使用すると、次のようになります。--left-right
git rev-list
2 1
出力として、オンになっていない2つのコミットがあり、オンにxyzzy
なっていないorigin/xyzzy
1つのコミットがあることを示しorigin/xyzzy
ますxyzzy
。これらはそれぞれcommits J
-and- I
(on xyzzy
)とK
(on origin/xyzzy
)です。
--count
オプションがないと、接頭辞(左)または(右)の記号git rev-list
が付いたハッシュIDが一覧表示されます。次のように、の代わりに使用します。<
>
git log
git rev-list
git log --left-right xyzzy...origin/xyzzy
(ここでも3つのドットに注意してください。gitrevisionsを参照して対称差を検索してください)3つのコミットが表示され、接頭辞<
または必要に応じて追加>
されます。
これは、ブランチにあるコミットとアップストリームにあるコミットを確認する簡単な方法です。 通常は、、、およびを使用するとより便利になり--decorate
ます(場合によっては追加することもできます)。--oneline
--graph
--boundary
前、後ろ、分岐、または最新
したがって、実行したとします。
git rev-list --count --left-right $branch...$upstream
(または、ここで再びgitrevisions$branch@{upstream}
を参照してください)、2つのカウントを取得しました。これらは次のようになります。
0
および0
:ブランチ名とリモートトラッキング名(またはアップストリームにあるもの)は同じコミットを指します。誰も前にも後ろにもいません。git status
コマンドは言うでしょうYour branch is up to date with '<upstream>'
。
ゼロ以外、ゼロ:現在のブランチにはアップストリームにないコミットがあります。現在のブランチにないアップストリームにはコミットはありません。したがって、私たちのブランチはアップストリームよりも進んでいます。
ゼロ、非ゼロ:アップストリームにない現在のブランチにはコミットはありませんが、現在のブランチにないアップストリームにはコミットがあります。これは、ブランチがアップストリームの背後にあることを意味します。
非ゼロ、非ゼロ:これは私が上に描いた図のようなものです。現在のブランチとそのアップストリームの両方が同時に前後にあります。コマンドはgit status
単語を使用しますdiverged
。
これから、元の質問に戻ります。現在のブランチのアップストレマがリモートトラッキング名であると仮定しましょう。 取得したカウントgit rev-list
は、リモート追跡ブランチ名の内容に基づいていることに注意してください。
これは実際にどのようにして起こりますか?
OPのシナリオでは、1人の人だけが新しいコミットを作成し、それらをで送信していgit push
ます。私が1人の場合、git clone
GitHubから何かを作成してから、新しいコミットを1つか2つ作成しgit push origin master
ます。
現代のGitでは、git status
私は最新の状態にあると教えてくれます。非常に古いバージョンのGitでは、git status
私のmaster
方が進んで origin/master
いることがわかります。 理由は単純です。昔は、git push
更新に失敗していましorigin/master
た。実行中git fetch origin
、または単にgit fetch
、独自のGitがGitHubでGitを呼び出し、その情報を読んで、自分が機能したことを認識しgit push
ました。
を実行するgit push
と、Gitに他のGitを呼び出させることができます。次に、Gitは他のGitに、あなたが持っている新しいコミットを提供しますが、そうではなく、完了する必要がありますgit push
。彼らはそれらのコミットを取り、どこかに置きます。7 次に、GitはGitに質問します(デフォルトでは丁寧に質問します)。よろしければ、ブランチ名に表示されているように、ハッシュIDで最新のコミットを参照するようにブランチ名を設定して ください。ここでは、リモート追跡機能はありません。使用しているのと同じ名前を設定するように依頼しているだけです。
原則として、リポジトリに新しいコミットを追加するだけの場合、この種の丁寧なリクエストは成功します。いくつかのコミットを「失う」ように依頼すると失敗します。これが「早送りではない」という苦情があります。新しいコミットを送信するのがあなただけの場合、この方法で失うものは決してないはずなので、これは常に機能するはずです。8
プッシュが失敗した場合は、Gitでリモートトラッキング名を変更しないでおくのが適切です。あなたのGitは、あなたのGitがそれを更新できるような情報をGitから取得することはありませんでした。しかし、プッシュが成功した場合...まあ、彼らはブランチ名をあなたのGitが彼らに使用するように頼んだハッシュIDに設定するだけです。これで、Gitはブランチ名がどこを指しているかを認識します。Gitはリモートトラッキング名を更新する必要があります。
古いGitバージョンでは、Gitはこれを気にしませんでした。Gitバージョン1.8.2では、Gitの作成者が最終的にこれを修正しました。成功git push
すると、Gitが提供する更新に同意したという事実に基づいて、Gitがリモートトラッキング名を更新します。したがって、この種のことはもうそれほど起こりません。
7古き良き時代には、彼らはそれらを直接リポジトリに入れました。最新のGitでは、それらを検疫エリアに配置し、実際に新しいコミットを受け入れた場合にのみリポジトリに移行します。
8もちろん、GitHubのような場所には、保護されたブランチなどの機能もあります。たとえば、すべてのプッシュにノーと言います。また、複数のコンピューターがあり、コンピューターAを介して新しいコミットを作成してプッシュしたことを忘れて、コンピューターBからプッシュしようとする場合など、より洗練されたシナリオを考案することもできます。
あなただけがやっていない場合はどうなりますかgit push
アリスとボブの両方がGitHubリポジトリのクローンを作成したとします。このリポジトリでの開発は、ブランチで行われますdev
(開発用)。だからアリスは彼女dev
から彼女自身を作りますorigin/dev
:
...--G--H <-- dev (HEAD), origin/dev [Alice's computer]
ボブも同様に、自分で作成しdev
ます。
...--G--H <-- dev (HEAD), origin/dev [Bob's computer]
GitHubリポジトリもdev
で終了しH
ます。(彼らにはありませんorigin/dev
:GitHubリポジトリはリモートトラッキング名を気にしません。)
アリスは新しいコミットを作成します。これをと呼びI
、アリスのコンピューターで次のように描画します。
I <-- dev (HEAD)
/
...--G--H <-- origin/dev
その間、ボブは新しいコミットを行います。これを次のように呼び出しますJ
。
...--G--H <-- origin/dev
\
J <-- dev (HEAD)
今、アリスとボブの両方がしようとしgit push origin dev
ます。そのうちの1人が最初にそこに着きます—おそらくアリス:
...--G--H--I <-- dev [GitHub's systems]
ボブはcommitを送信しますJ
。これはGitHubに次のようになります。
I <-- dev
/
...--G--H
\
J ... polite request to set dev to J
I
GitHubがそれを行う場合、Gitは名前から開始して逆方向に作業することでコミットを検出するため、これはAliceのコミットを「失います」 。したがって、彼らは「早送りではない」という苦情でプッシュを拒否します。
I
ボブは、GitHubからボブ自身のリポジトリにコミットをヤンクする必要があります。これにより、ボブは次のことを確認できます。
I <-- origin/dev
/
...--G--H
\
J <-- dev (HEAD) [Bob's computer]
ボブは、git fetch
またはgit fetch origin
、おそらく--prune
(またはにfetch.prune
設定してtrue
)でこれを行う必要があります。 ボブが実行すると、「発散した」メッセージが表示されます。git status
プッシュレースの敗者であるボブが、自分の作業(コミットJ
)とアリスの作業(コミット)を組み合わせる方法を理解するのは、今やボブ次第I
です。仕事を組み合わせるにはさまざまな方法があります。2つの主要なものはgit merge
とgit rebase
です。ここでは、誰が、いつ、なぜ何をすべきかについては説明しません。これは、他のGitよりも厳密に進んでいると思ったときに、これが「発散」状態に陥る可能性がある別の方法であるという事実だけです。