注:この特定の質問は、私がこの回答を書いている時点では、かなり古いものです。これらの問題の多くを修正したバージョンの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-togit branch --unset-upstream
これらは、現在のブランチ--set-upstream-toに--unset-upstream影響します。現在のブランチは、あなたが言っているとき、またはそれが何を言っているときでも、あなたがいるブランチです。このブランチは、または(Git 2.23以降)<code>gitスイッチを使用して選択します。2 チェックアウトしたブランチが何であれ、それが現在のブランチです。3ongit statuson branch xyzzygit checkout
を使用する--unset-upstreamと、現在のブランチのアップストリームが削除されます。アップストリームがない場合、これにより、先行または遅延、または分岐に関するメッセージが停止します。ただし、このメッセージは役立つことを目的としているため、アップストリームを削除して、発生を停止させるべきではありません。(メッセージが役に立たない場合は、メッセージを無視してください。結局のところ、エラーではありません。)
を実行するgit branch --set-upstream-to=origin/xyzzyと、現在のブランチのアップストリームがに設定されorigin/xyzzyます。という名前のブランチの場合xyzzy、これが一般的な正しい設定になります。ブランチを作成する動作には、(通常は正しい)アップストリームが自動的に設定されるものと、そうでないものがあります。したがって、正しいアップストリームを自動的に設定するブランチ作成操作を使用した場合は、何もする必要はありません。別のアップストリームが必要な場合、またはアップストリームを設定しないブランチ作成操作を使用した場合は、これを使用してアップストリームを変更できます。
アップストリームに設定できるものは次のとおりです。
- 別の独自の(ローカル)ブランチ:現在のブランチの上流に
git branch --set-upstream-to=experiment独自のローカルを作成します。experimentまた
- またはなどのリモートトラッキング名のいずれか。これらは、によって出力される名前です。Gitはこれらのリモートトラッキングブランチ名を呼び出します(ここに「ブランチ」という単語をドロップするのが好きです)。これについては後で詳しく説明します。
origin/mainorigin/masterorigin/xyzzygit 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 checkoutgit switchgit 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
しかし、問題があります。それらのmainor master、またはそれらがxyzzyある場合は、必ずしもあなたのorまたは。と同じコミットを意味するわけではありません。ただし、解決策は簡単です。Gitはブランチ名を取得し、それをリモートトラッキング名に変換します。 mainmasterxyzzy
origin'smainまたはmasterorが移動した場合は、単にorxyzzyを実行します。おそらく。を使用します。あなたのGitは彼らのGitを呼び出します。ブランチ名をリストし、ハッシュIDをコミットします。あなたのGitは、必要に応じて、彼らから新しいコミットを取得します。彼らが持っているコミットと、あなたが持っていないコミットです。次に、Gitはブランチ名をリモートトラッキング名に変換し、リモートトラッキング名を作成または更新して、これを実行した時点でブランチ名が指している場所を記憶します。git fetchgit fetch origin--prunegit fetch
を使用すると、ブランチ名が削除され--pruneた場合に対応します。。という名前のブランチがあったとしましょう。あなたは以前にそれを手に入れたので、あなたはあなたのリモートトラッキング名を持っています。それから彼らは削除したので、今回は...もう持っていません。がないと、Gitはこれを無視します。あなたは今死んでいるのに あなたの古いものを保ちます。を使用すると、Gitは次のように言います。ああ、これは今は死んでいるように見え、それを削除します。Git内の、ブランチ名の1つに対応しないリモート追跡名は、削除されるだけです。oldstufforigin/oldstuff oldstuff--pruneorigin/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-list2つの数字を取得する方法
以前、私は、印刷される前、後ろ、最新、または分岐したメッセージが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は両方の名前から到達可能です。HH
これにより、コミットはI-J*名前からのみ到達可能であるという点で特別にxyzzyなり、 *名前からのみ到達可能であるという点で特別になります。3ドット表記は、これらのコミットを検出します。一方の名前からのみ到達可能なコミット、またはもう一方の名前からのみ到達可能なコミットです。Korigin/xyzzy
ブランチ名を左側に、そのアップストリームを右側に配置し、3ドット表記を使用すると、この場合のこれら3つのコミットすべてが見つかります。を使用--countすると、次の数値が出力されます。3 git rev-list.使用すると、よりスマートになります。ただし、左側の名前(現在のブランチ名)のためにカウントされているコミットの数と、右側の名前(右側の名前)のためにカウントされているコミットの数をカウントする必要があります。上流の。したがって、両方のオプションと3つのドットを使用すると、次のようになります。--left-rightgit rev-list
2 1
出力として、オンになっていない2つのコミットがあり、オンにxyzzyなっていないorigin/xyzzy1つのコミットがあることを示しorigin/xyzzyますxyzzy。これらはそれぞれcommits J-and- I(on xyzzy)とK(on origin/xyzzy)です。
--countオプションがないと、接頭辞(左)または(右)の記号git rev-listが付いたハッシュIDが一覧表示されます。次のように、の代わりに使用します。<>git loggit 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 cloneGitHubから何かを作成してから、新しいコミットを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
IGitHubがそれを行う場合、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よりも厳密に進んでいると思ったときに、これが「発散」状態に陥る可能性がある別の方法であるという事実だけです。