11

私がやりたいこと:

マージコミットを中央リポジトリにプッシュすることを禁止したい。唯一の例外は、中央リポジトリに存在するブランチ間でマージが行われる場合です。これを中央リポジトリで実施したいと思います。

私がこれをしたい理由の説明:

注:この説明によって、私がやりたいことの道から外れてしまう場合は、説明を無視してください。もちろん、以下で説明する問題を解決する他の方法を聞いてうれしいですが、私が興味を持っている答えは、上記のようにやりたいことです。

いくつかの開発者が追跡するブランチを持つ中央のgitリポジトリがあります。各開発者は、その中央リポジトリのブランチ用にリモートを構成しています。

このプロジェクトの同期コミットポリシーに従うため、各開発者は、プッシュする前に、常に最新の作業をリモートブランチHEADの上にリベースする必要があります。マージコミットが中央リポジトリにプッシュされないようにすることで、このポリシーを適用したいと思います。唯一の例外は、中央リポジトリに存在するブランチ間でマージが行われる場合です。

簡単にするために、開発者のローカル追跡ブランチがリモートブランチとマージされることは望ましくありません。ただし、常にリモートブランチに基づいてリベースします。

branch.NAME.rebase = trueを設定することで、開発者のマシンにこれを部分的に適用しました。これは、開発者がgit pullを使用する場合の問題を回避するのに役立ちますが、中央リポジトリ側でこれを適用するソリューションが必要です。

非常に基本的な解決策は、「GITURLのブランチ'NAME'をマージする」というコメントでコミットを拒否することですが、コミットのすべての親が中央リポジトリのブランチパスに存在するかどうかをチェックするという方針に沿った何かがより興味深いでしょう。

提案?ソリューション?

編集:

これは私がこれまでに持っているものです:

#!/bin/sh
read sha1old sha1new refname

# check if this is merge commit
merge_commit="`git rev-list --parents --merges --no-walk $sha1new 2> /dev/null`"
if test -n "$merge_commit"
then
  # this was a merge commit
  # $merge_commit contains: sha1new sha1parent_1 ... sha1parent_n
fi
exit 0

厄介なのは、2人の親の祖先が1つのブランチに由来するかどうかを判断することです。また、refが更新される前にpre-receiveフックが呼び出されるため、プッシュにリモートに存在する2つのブランチのコミットが含まれている場合(これら2つのブランチ間のマージを含む)、ここでの解決策はわかりません。 。

4

1 に答える 1

11

非線形の履歴を作成するプッシュを防止する 1 つの方法は、複数の親を持つ新しいコミットをチェックし、そうであればエラーで終了するためpre-receiveに使用するフックを設定することです。git rev-list --parents <OLD>..<NEW>2 番目の要件に対処するには、代わりに、複数の親がある場合、それらのコミットはすべてリポジトリ内の既存のブランチにある必要があることを確認できます。私はこれをあまりテストしていませんが、このpre-receiveフック (またはその変形) が必要な場合があります。

#!/usr/bin/ruby -w

# A pre-receive hook that should refuse any pushes that would update
# master in such a way that a non-linear history would be created,
# except where it involves a merge from another branch in this
# repository.  This has only had very cursory testing.

# This is a suggested answer to:
#   http://stackoverflow.com/questions/2039773/have-remote-git-repository-refuse-local-branch-merge-commits-on-push

ref_to_check = "refs/heads/master"

rev_old, rev_new, ref = STDIN.read.split(" ")

if ref == ref_to_check
  merge_bases = `git merge-base #{rev_old} #{rev_new}`.strip.split(/\s+/)
  unless $?.success? and merge_bases.length == 1
    STDERR.puts "No unique merge base found between #{rev_old} and #{rev_new}"
    exit(1)
  end
  rev_list_output = `git rev-list --parents #{merge_bases[0]}..#{rev_new}`
  list_of_revs_with_parents = rev_list_output.strip.split(/[\r\n]+/)
  list_of_revs_with_parents.each do |line|
    rev_with_parents = line.strip.split(/\s+/)
    if rev_with_parents.length > 2      
      parents = rev_with_parents.slice(1,rev_with_parents.length)
      # The question says to permit non-linear history if the merge is
      # from another branch in the central repository, so check
      # whether that's the case.  (If you just want to prevent all
      # pushes that add non-linear history, just exit with error
      # here.)
      any_parent_not_on_any_branch = false
      parents.each do |p|
        branches = `git branch --contains #{p} 2> /dev/null`
        if $?.success? and ! branches.strip.empty?
          STDERR.puts "More than one parent of commit #{rev_with_parents[0]}"
          STDERR.puts "... but parent #{p} is on branches:"
          STDERR.puts branches
        else
          STDERR.puts "Parent #{p} not found on any other"
          STDERR.puts "branch in this repository"
          any_parent_not_on_any_branch = true
          break
        end
      end
      if any_parent_not_on_any_branch
        STDERR.puts "Refusing push, since it would create non-linear history"
        STDERR.puts "for #{ref} and the merges don't just involve commits on"
        STDERR.puts "other branches in this repository."
        exit(2)
      end
    end
  end
end

それが何かの役に立てば幸いです。

于 2010-01-17T14:48:49.733 に答える