2016-07-06 13 views
2

私は多くのコミットを持つ開発ブランチを持っています。これらのコミットには、実際の変更(機能の追加など)と一時的な変更(たとえば、テストコードを1回のコミットで追加した後、後でコミットするなど)が含まれます。このブランチからの実際の変更は、徐々にマスタに追加されています。追加のたびに、私は新しいマスターの開発ブランチをリベースします。サイクルごとに、開発ブランチとマスターの違いは小さくなっています。ただし、リベースされた開発ブランチには、元の(現在リベースされている)コミットがすべて含まれています。これらのコミットのうちのいくつかは、現在、ネット効果がゼロです。自動的にコミットを削除する方法Gitで自分自身をキャンセルするコミット

簡単な例:この時点で

DEV_BRANCH 
    Commit ... 
    Commit D: Remove all test code from f.cpp 
    Commit ... 
    Commit C: Add new feature to f.cpp 
    Commit ... 
    Commit B: Add more test code to f.cpp 
    Commit A: Add some test code to f.cpp 
    Commit ... 
MASTER 
    Commit X: Add new feature to f.cpp 
    Commit ... 

、コミットCからf.cppへの変更はXをコミットするように既ににマスターし、合わせたときA + B + Dをコミット変化がありません〜f.cpp。言い換えれば、ブランチとマスター間のdiffは、ファイルf.cppに対して何も表示しません。

(実際には、A、B、Cを、コミットおよびDは、他のファイルへの変更を含んでいてもよい。)

が自動的にリベース中またはその他のいずれかの開発ブランチにコミットを簡素化する方法はあります?上記の簡単な例で

は、に自動的Cコミット除去(変化と既に今「空」従って習得するマージ)、また、B、及びD(合わせ変化なし)をAコミットすることが可能です?

さらに複雑なシナリオでは、f.cppが他のファイルも変更すると、開発ブランチ内のコミットから自動的にファイルf.cppの変更を削除することができます(簡略化するため)。それらが他のファイルへの変更を含んでいる場合、存在しますか?

改訂版では、開発ブランチのすべてのコミットのファイルにすべての変更を適用すると、マスターのファイルと同じファイルになりますが、開発ブランチからこのファイルへの変更をプルーニングできますか? (これは、他のファイルへの変更を、ネット効果のないファイルへの変更と同期させる必要がある場合、これが副作用につながる可能性があることを認識しています。しかし、これは私のシナリオでは問題ありません。 - 開発ブランチの中間の状態にピックします。)

+0

@quetzalcoatlはい、私は一般にこれは難しい問題であることを認識しています。しかし、私の状況は制約されているように見えますが、devブランチ自体に中間的な状態は必要ありません。私はマスターから新しいブランチを作成し、オリジナルのdevブランチからすべての変更されたファイルをコピーすることを考えました。しかし、これは1つの新しい一括コミットを作成し、すべての細分性を失います(実際の変更をすべて実行するまでは便利です) –

+1

はこれを考えています。コミット後フィーチャ・オン・マスターとrebase-devel-あなたはdevelとmasterの間で 'merge --squash --no-commit --no-ff'を行うだけで、どのファイルが実際に変更されたかを検出できます。これにより、エフェクトを変更したファイルの一覧が表示されます。** chg **と呼びます。mergeを中止し、HEADのダウンストリームマスターから 'filter-branch'(removeemptycommits)でdevelの履歴を参照し、各コミットを編集してファイルを削除します**これはchg **にはありません。これは、適切な変更だけでなく、誤ったポジションでもコミットを残します。 – quetzalcoatl

+0

その部分は幾分清潔で、簡単で、指数関数的な複雑さなどではなく、おそらくbashスクリプトとして実装可能です。しかし、偽陽性を解決することは元の問題と同じくらい難しいようです。楽観的です大文字小文字の問題ではなく、実際の解決策ではありません – quetzalcoatl

答えて

1

私は、コメントの中で複雑な問題に対処している人がいることが分かります。

問題はあまりにも困難なので一般的には不可能です。問題を「空」(直前から変更していない)と定義した場合は簡単ですが、実際にはすでにGitにあります。--keep-emptyを要求しない限りrebaseは既にそれを行います。

(これは、ステロイドのようなリベースのようなものですが、デフォルトは逆ですが、--prune-emptyを指定しないとそのコミットを保持します)。コミットフィルタを使用している場合、実際にgit commit-treeを呼び出す前に現在のツリーと前のツリーを単純に比較するgit_commit_non_empty_treeを使用してください。この場合、git filter-branchはおそらくあなたが望むよりも重量が重いでしょう。)コメントで

、あなたが書いた:

私は、マスターから新しいブランチを作成して、オーバー私の元のdevの枝から変更されたすべてのファイルをコピーすると考えます。しかし、これは1つの新しいしこりがコミット作成し、すべての粒度失い

Gitのは、このために設計されている(私はすべての本当の変更を選んで行っていますまで、便利である持っている。):ちょうど「切り離し」HEAD(git checkout --detachまたはチェックアウト生のSHA-1 IDによって)あなたが好きな一時的なコミットを作ります。名前を付けられたブランチに戻った後は、HEAD reflog(デフォルトの到達不能コミットの場合は30日)で保護されている限り、一時コミットはそのまま残ります。 (まあ、ルーズオブジェクトであれば14日の猶予期間がありますが、14は30未満です)

コミットする必要はありません。 git diff(またはgit diff-treemasterの先端にあるツリーと、dev_branchの先端にあるツリー。言い換えれば、完全なソースツリーの両方の形式は、2つの個別のコミットとして既にGitにあります。彼らはそれらの間に(他のコミットの)履歴があります:

しかし、それらを直接比較することはできません。取り外したHEADのトリックは、これを行うために有用である:あなたが戻ってヘッドを移動するgit resetgit checkoutを使用して、それを好きではない場合は、その後、破棄することができます*は、いくつかの(!実際にコミット)コミット提案である

  o-------o <-- master 
     /
...--o--o------*  <-- detached HEAD 
     \ 
      o--o--o--o <-- dev_branch 

、他のコミットに、またはブランチ名を指すように戻すことができます。

(実際には、git rebaseはその作業を行います:新しいブランチが成長するポイントでHEADを切り離し、新しいブランチが完了するまでコミットを追加してからブランチ名から元のtip-mostをコミットして、矢印が新しいtip-most commitを指し示すようにします)。

+0

はい、本当に空のコミットは(デフォルトでは)リベース中は保持されないと思います。ちょっと(http://stackoverflow.com/a/5324916/2325279にも触発された)私はコミットフィルタを実行しました。それは時間がかかりましたが、何も簡素化しませんでした。 –

2

私は、あなたの特定のユースケースに対してかなり簡単な解決策がいくつかあると考えています。最初の場所。

解決策1:コミットの数が少ないために発散する枝に

のは、あなたがgit rebase --interactiveモードに示すdevにコミットの次のセットがあるとしましょう:

pick bf45b13 Add feature X 
pick b1f790f Cleanup feature X 
pick 40b299a Add feature Y 

今、あなたは桜に決定40b299amasterに追加します。

devをリベースするときに新しいコミットが空になるという問題があります。今後のリベースにはgit rebase -i masterをお勧めします。チェリーを選択した行を削除し、必要なコミットだけを残しておくことができます。

これを「自動的に」実行したいので、独自のgitスクリプトを作成してチェリーピックとリベースを同時に行うことができます。devから正しいコミットを削除してマスターに追加します。あなたはgit-transferなどは、以下のスクリプトの何かを呼び出して、あなたのPATHにそれを追加した場合

どこかで、あなたはgit transfer dev master commit [...]としてそれを呼び出すことができます。

#!/bin/bash 

usage() { 
    echo "Usage: git transfer from-branch to-branch commits [...]" 
    if [ -n "${1}" ] 
    then 
     echo 
     for line; do echo "${line}"; done 
    fi 
    exit 1 
} 

FROM="$(git rev-parse --abbrev-ref "${1}")" 
[ -z "${FROM}" ] && usage 'from-branch must be a valid branch name' || shift 
TO="$(git rev-parse --abbrev-ref "${1}")" 
[ -z "${TO}" ] && usage 'to-branch must be a valid branch name' || shift 

ORIGINAL="$(git rev-parse --abbrev-ref HEAD)" 

if [ "${ORIGINAL}" == "${TO}" ] 
then 
    echo "Already on branch ${TO}" 
else 
    echo "Switching from ${ORIGINAL} to ${TO}" 
    git checkout "${TO}" || exit 
fi 

while [ $# -gt 0 ] 
do 
    for COMMIT in "$(git rev-parse "${1}" | grep '^[^^]')" 
    do 
     echo "Moving ${COMMIT} to ${TO}" 
     git cherry-pick "${COMMIT}" 
     echo "Removing ${COMMIT} from ${FROM}" 
     EDITOR="sed -i '/^pick $(git rev-parse --short ${COMMIT})/ d'" git rebase -i "${TO}" "${FROM}" 
    done 
    shift 
done 

if [ "${ORIGINAL}" != "${TO}" ] 
then 
    echo "Switching back to ${ORIGINAL} from ${TO}" 
    git checkout "${ORIGINAL}" 
fi 

スクリプトがなる「から」ブランチの名前を受け入れますチェイスピックされる "to"ブランチの名前、コミット、コミットレンジなどのリスト。これは、questionanswerで提案されたアイデアに大きく基づいています(ループの場合はanswer) 。

このソリューションは、分岐が著しく分岐した場合と同様に、少ない回数のコミット(一度に1つ)に適しています。

解決策2:あなたが説明した使用例では、単一のタイムライン上のコミット数が多い

について、あなたは定期的にmasterdevをリベースので、devが効果的に先マスターのすべての時間です。

  1. が対話型リベースを開始し
  2. リスト
  3. コンプリートリベース
  4. 移動マスターへの始まりにしたいコミットを動かし:代わりに、個人を選ぶの次の時間がマスターにコミットし、次の操作を行いますあなたが望む最後のコミット。

この方法では、重複を避けることができ、おそらく最も簡単な方法です。 devは常にリベースされますが、masterはこのアプローチでのみ早送りされます。

+0

私はコミットを並べ替えるというあなたの考えが好きです。残念なことに、私の支店では多くの混同されたコミットのため、これは私のためには機能しません。 @PetrVepřek。 –

+1

なぜ私は理解しているか分からない。基本的には、とにかくそれらのコミットのいくつかを選択しています。選択の一部としてリベースを行うのは重要ですか? –

+0

@PetrVepřek: '私のブランチにたくさんの混乱したコミットがあるから' - あなたは、rebase中にコミットの修正やマージ(fixup、squashなど)だけでなく、編集や分割もできます。分割コミットは1つのファイル(ここでは1つのファイル)を意味するわけではなく、特定のチャンク/ラインを選択したり、新しいテキストを挿入したり、間にコミットすることもできます。これは時にはかなりの量の(手動)作業ですが、しばしば古い「不注意な」コミットを整理し直すのに役立ちます。 – quetzalcoatl

関連する問題