web-dev-qa-db-ja.com

作業コピーを変更せずにGitブランチをリベースできますか?

「master」ブランチをチェックアウトしたとします。 「master」にいくつかのプロダクションの変更をコミットしました。「実験的」ブランチを最新のマスターにリベースしたいと思います。しかし、私は作業コピーのファイルを変更せずにこれを実行したいと考えています。基本的に、私はすべての魔法が作業コピーに触れることなく、.gitディレクトリ内で発生するようにします。

「自分の作業コピーを変更しない」という要件がない場合、これは単に問題になります。

# current branch is master
git checkout experimental
git rebase master
git checkout master

私の本当の問題は、作業コピーのタイムスタンプが変更されることです。ただし、最初とまったく同じコンテンツをチェックアウトして終了しています。 「git checkout experimental」を実行するとすぐに、experimentalブランチでの変更を含むすべてのファイルのmtimeが現在の時刻に設定されます。つまり、前回私が実験をリベースしてからマスターで変更されたファイルも同様です。 mtimesが変更されたため、ビルドツールのようなものには、もう一度実行する必要がある作業があることがわかりますが、私が完了した時点では、ファイルの内容は実際には変更されていません。 (私の場合、プロジェクトファイルのタイムスタンプが変更された場合、Visual Studioは、プロジェクトのアンロードと再ロードに多くの時間を費やす必要があると判断します。)それを避けたいのです。

作業コピーの内容を一切変更せずに、上記のすべてを1つのステップで実行する方法はありますか(リベース中に競合がないと仮定して)

areが競合する場合、タイムスタンプを変更せずにエラーを表示し、操作全体を中止するのが私の好みですが、それは私の好みです、難しい要件ではありません-何が可能かわかりません)。

もちろん、mtimesをキャプチャしてgitを実行し、mtimesをリセットするスクリプトを書くこともできます。しかし、リベースは実際にはファイルの実際の内容ではなく、デルタに関するものであるため、Gitにはすでに作業コピーを煩わせることなくリベースのようなことをする方法があると思われます。

62
Joe White

作業コピーの何も変更せずに、上記のすべてを1つのステップで実行する方法はありますか?

これは残念ながら不可能です(作業コピーの変更可能なコピーを作成しない限り- Petrの回答も参照してください )、gitはすべてのmerge-y操作(実際のマージ)を実行するため、チェリーピック、リベース、パッチアプリケーション)これは以前にも何度か言及されています。たとえば、 知識のあるJakubNarębskiの回答の1つ で:

作業ディレクトリ(および/またはインデックス)を使用して解決する必要があるマージの競合が存在する可能性があるため、作業ディレクトリ(およびインデックス)に触れずにマージ(またはリベース)を機能させる方法はありません。

はい、それは設計上の決定ですが、それはかなり理解できるものです-メモリでマージを試みるために必要なすべての構造を構築するのは少し面倒です、そしてそれが衝突に遭遇したらすぐにすべてをにダンプします作業ツリー。代わりに、そもそも作業ツリーで簡単に実行できます。 (私はgit開発者ではありません。これを絶対的な完全な真実と見なさないでください。他の理由が考えられます。)

すべてのmtime操作を行うスクリプトを作成するのではなく、リポジトリを複製し、複製でリベースを実行してから、元のリポジトリにそれをプッシュすることをお勧めします。

git clone project project-for-rebase
cd project-for-rebase
git branch experimental Origin/experimental
git rebase master experimental
git Push Origin experimental

もちろん、これは実験が元のリポジトリでチェックアウトされていないことを前提としています。場合は、プッシュの代わりに、git fetch ../project-for-rebase experimental; git reset --hard FETCH_HEAD以上の読みやすいgit remote add for-rebase ../project-for-rebase; git fetch for-rebase; git reset --hard for-rebase/experimentalのようにします。これは、元の実験ブランチとリベースされた実験ブランチの間で異なるファイルに自然に影響しますが、それは間違いなく正しい動作です。 (もちろん、これはあなたが挙げた例ではありませんが、これらの指示を一般的なものにしたいと思います!)

32
Cascabel

git 2.5 なので、さらに優れたソリューションは、2番目の worktree を使用することです。

Gitリポジトリは複数の作業ツリーをサポートできるため、一度に複数のブランチをチェックアウトできます。

$ git worktree add ../second-copy experimental
$ cd ../second-copy/
$ git rebase master experimental

以上です。その後、rm -rf second-copy必要に応じて、または今後のリベースのために保持します。

$ git rebase master experimental
29
Craig P. Motlin

Linuxでこれを行うための小さなスクリプトを作成しました。これはJefromiの回答といくつかの追加に基づいています(主に、オブジェクトデータベースがコピーされないように代替を設定し、必要なブランチのみをプルします)。あなたの一部はそれを便利だと思うかもしれません: https://github.com/encukou/bin/blob/master/oot-rebase

それがあなたの好きなことをまったくしないなら、プルリクエストは大歓迎です:)

11
Petr Viktorin

リポジトリのクローンを作成するのと同様に、このようなことを行うために複数のワークディレクトリを使用するほうがずっと簡単です。また、クローンは多くのディスク領域を使用しますが、これはほとんど使用しません。

https://github.com/git/git/blob/master/contrib/workdir/git-new-workdir

次のように新しいワークディレクトリを作成します。

git-new-workdir project-dir new-workdir branch

その後、元のワークディレクトリでのフェッチとコミットがここに反映されることを除いて、それをクローンのように扱うことができます(ただし、再チェックアウトなしの作業ブランチには反映されません)。これに対する唯一の例外は、デフォルトで各ワークディレクトリに対して個別に行われるサブモジュールがある場合です。率直に言って、サブモジュールを避けようとしているので、これまで調べたことはありません。

だから基本的には:

cd new-workdir
git checkout experimental
git rebase master

正確には単一のコマンドではありませんが、かなり単純です。

以下のスタッシュアプ​​ローチに対するこのアプローチ(クローンアプローチのような)の利点は、作業ディレクトリから現在実行中の(または一部のプロセスで使用されている)コードがある場合、中断されないことです。


ここで言及されていないもう1つのオプションは、現在の作業ディレクトリで実行することですが、変更を隠して、作業ディレクトリの状態を即座に復元できるようにします。

# current branch is master (with changes to working state)
git stash -u
git checkout experimental
git rebase master
git checkout master
git stash pop

新しいファイルがある場合は、必ずstash -uを使用してください。そうしないと、ファイルが隠されなくなります。繰り返しますが、1つのステップではありませんが、かなりクリーンでシンプルです。

5
dpwrussell

他の人が言ったように、作業ディレクトリに触れずにブランチをリベースすることはできません(新しいクローンやワークツリーを作成するなどの提案された代替案でもこの事実を変更することはできません。これらの代替案は実際に現在の作業ディレクトリに触れませんが、新しいワークツリーの作成)。

更新したいブランチが現在の作業ツリー(またはその親)に基づく場合は、ファイルを不必要に操作することなく他のブランチを「リベース」することができます。
この「特殊なケース」は、すべての「マスター」ブランチ(定期的にリモートマスターブランチに更新される)からすべてブランチされる多数のブランチで作業しているgitワークフローを使用している場合によく発生します。

説明のために、次の構造を持つGitリポジトリを想定します。

repo
- commitA
- commitB
- commitC <-- master <-- OtherBranch based on master
  - commitD <-- First commit in otherBranch
  - commitE <-- Second commit in OtherBranch
- commitD <-- Unrelated commit in current working tree

例として、「OtherBranch」が「master」から分岐し、現在の作業ツリーも「master」に基づいていると仮定します。ワークフローは通常、ローカルマスターブランチをリモートバージョンで更新することから始まります...

# Fetch commits from remote "Origin" and update the master branch:

# If your current branch is identical to master
git pull Origin master

# If your current branch has extra commits on top of master
git pull --rebase Origin master

# If you don't want to touch your current branch
git fetch Origin master:master

...次に、現在のブランチをいじって、時間のかかるコンパイルをいくつか行います。最終的には、OtherBranchで作業することを決定します。このOtherBranchmasterに基づいてリベースする必要があります(最小限のファイルシステム操作で)。次のセクションでその方法を示します。

他のブランチのリベース(参照例-これを行わないでください)

次の解決策は、それを行うgitの方法です。

git checkout OtherBranch
git rebase master    # or git rebase Origin/master

その欠点は、ファイルが2番目のコマンドによって復元される場合でも、最初のコマンドが現在のワークツリーの日付を変更することです。

最小限の変更で他のブランチをリベースする

タッチされるファイルの数を最小限に抑えるには、新しいベースブランチにチェックアウトし、 git cherry-pick を使用して、ベースブランチの上にあるOtherBranchの追加のコミットをすべて適用する必要があります。 。

何かを行う前に、OtherBranchのコミットを特定する必要があります。

  • git log OtherBranchは、OtherBranchでのコミットを示します(主にOtherBranchをまだ変更していない場合に役立ちます)
  • git reflogは、ローカルリポジトリ内のブランチへの変更を示します(すでにブランチを更新していて間違いがあった場合に便利です)。

現在の例では、OtherBranchの最後のコミットがcommitEであることがわかります。その前にgit log commitEでコミットのリストを表示できます(または、より短いリストが必要な場合はgit log --oneline commitE)。リストを見ると、ベースコミットがcommitCであることがわかります。

これで、ベースコミットがcommitCで、最後のコミットがcommitEであることがわかったので、次のようにして、OtherBranchを(以前の「マスター」から新しい「マスター」に)リベースできます。

# Replace the old OtherBranch with "master" and switch to it.
git checkout -B OtherBranch master

# Cherry-pick commits starting from commitC and ending at commitE.
cherry-pick commitC^..commitE

または、OtherBranchを置き換える前に「リベース」を正常に完了したい場合):

# Create new branch NewOtherBranch based off "master" and switch to it.
git checkout -b NewOtherBranch master

# Cherry-pick commits starting from commitC and ending at commitE.
cherry-pick commitC^..commitE

# Replace the old branch with the current branch (-M = --move --force)
git branch -M OtherBranch

なぜこれが機能するのですか?

Gitでブランチをリベースするには、現在のブランチを更新するブランチ(OtherBranch)に切り替える必要があります。

git rebaseワークフローでは、次のことが起こります。

  1. OtherBranchに切り替えます(非常に古いベースブランチから分岐している可能性があります)。
  2. リベース(内部ステップ1):上流のブランチにないコミットを保存します。
  3. リベース(内部ステップ2):現在のブランチを(新しい)ベースブランチにリセットします。
  4. リベース(内部ステップ3):ステップ2からコミットを復元します。

ステップ1とステップ3は多くのファイルに触れますが、結局、触れられたファイルの多くは実際には変更されていません。

私の方法は、ステップ1と3をステップ3に組み合わせたもので、その結果、タッチされたファイルの数は最小限に抑えられます。変更されるのは、次のファイルのみです。

  • 現在の作業ツリーのベースブランチと現在のコミットの間で変更されたファイル。
  • OtherBranchのコミットによって変更されたファイル。
1
Rob W

私も大好きですが、これには希望がありません。

<branch>が指定されている場合、git rebaseは他の処理を行う前に自動gitチェックアウトを実行します。それ以外の場合は、現在のブランチに残ります。

http://git-scm.com/docs/git-rebase

1
Snowbear