rlm@11: #!/bin/sh rlm@11: # rlm@11: # Copyright (c) 2006, 2008 Junio C Hamano rlm@11: # rlm@11: # The "pre-rebase" hook is run just before "git-rebase" starts doing rlm@11: # its job, and can prevent the command from running by exiting with rlm@11: # non-zero status. rlm@11: # rlm@11: # The hook is called with the following parameters: rlm@11: # rlm@11: # $1 -- the upstream the series was forked from. rlm@11: # $2 -- the branch being rebased (or empty when rebasing the current branch). rlm@11: # rlm@11: # This sample shows how to prevent topic branches that are already rlm@11: # merged to 'next' branch from getting rebased, because allowing it rlm@11: # would result in rebasing already published history. rlm@11: rlm@11: publish=next rlm@11: basebranch="$1" rlm@11: if test "$#" = 2 rlm@11: then rlm@11: topic="refs/heads/$2" rlm@11: else rlm@11: topic=`git symbolic-ref HEAD` || rlm@11: exit 0 ;# we do not interrupt rebasing detached HEAD rlm@11: fi rlm@11: rlm@11: case "$topic" in rlm@11: refs/heads/??/*) rlm@11: ;; rlm@11: *) rlm@11: exit 0 ;# we do not interrupt others. rlm@11: ;; rlm@11: esac rlm@11: rlm@11: # Now we are dealing with a topic branch being rebased rlm@11: # on top of master. Is it OK to rebase it? rlm@11: rlm@11: # Does the topic really exist? rlm@11: git show-ref -q "$topic" || { rlm@11: echo >&2 "No such branch $topic" rlm@11: exit 1 rlm@11: } rlm@11: rlm@11: # Is topic fully merged to master? rlm@11: not_in_master=`git-rev-list --pretty=oneline ^master "$topic"` rlm@11: if test -z "$not_in_master" rlm@11: then rlm@11: echo >&2 "$topic is fully merged to master; better remove it." rlm@11: exit 1 ;# we could allow it, but there is no point. rlm@11: fi rlm@11: rlm@11: # Is topic ever merged to next? If so you should not be rebasing it. rlm@11: only_next_1=`git-rev-list ^master "^$topic" ${publish} | sort` rlm@11: only_next_2=`git-rev-list ^master ${publish} | sort` rlm@11: if test "$only_next_1" = "$only_next_2" rlm@11: then rlm@11: not_in_topic=`git-rev-list "^$topic" master` rlm@11: if test -z "$not_in_topic" rlm@11: then rlm@11: echo >&2 "$topic is already up-to-date with master" rlm@11: exit 1 ;# we could allow it, but there is no point. rlm@11: else rlm@11: exit 0 rlm@11: fi rlm@11: else rlm@11: not_in_next=`git-rev-list --pretty=oneline ^${publish} "$topic"` rlm@11: perl -e ' rlm@11: my $topic = $ARGV[0]; rlm@11: my $msg = "* $topic has commits already merged to public branch:\n"; rlm@11: my (%not_in_next) = map { rlm@11: /^([0-9a-f]+) /; rlm@11: ($1 => 1); rlm@11: } split(/\n/, $ARGV[1]); rlm@11: for my $elem (map { rlm@11: /^([0-9a-f]+) (.*)$/; rlm@11: [$1 => $2]; rlm@11: } split(/\n/, $ARGV[2])) { rlm@11: if (!exists $not_in_next{$elem->[0]}) { rlm@11: if ($msg) { rlm@11: print STDERR $msg; rlm@11: undef $msg; rlm@11: } rlm@11: print STDERR " $elem->[1]\n"; rlm@11: } rlm@11: } rlm@11: ' "$topic" "$not_in_next" "$not_in_master" rlm@11: exit 1 rlm@11: fi rlm@11: rlm@11: exit 0 rlm@11: rlm@11: ################################################################ rlm@11: rlm@11: This sample hook safeguards topic branches that have been rlm@11: published from being rewound. rlm@11: rlm@11: The workflow assumed here is: rlm@11: rlm@11: * Once a topic branch forks from "master", "master" is never rlm@11: merged into it again (either directly or indirectly). rlm@11: rlm@11: * Once a topic branch is fully cooked and merged into "master", rlm@11: it is deleted. If you need to build on top of it to correct rlm@11: earlier mistakes, a new topic branch is created by forking at rlm@11: the tip of the "master". This is not strictly necessary, but rlm@11: it makes it easier to keep your history simple. rlm@11: rlm@11: * Whenever you need to test or publish your changes to topic rlm@11: branches, merge them into "next" branch. rlm@11: rlm@11: The script, being an example, hardcodes the publish branch name rlm@11: to be "next", but it is trivial to make it configurable via rlm@11: $GIT_DIR/config mechanism. rlm@11: rlm@11: With this workflow, you would want to know: rlm@11: rlm@11: (1) ... if a topic branch has ever been merged to "next". Young rlm@11: topic branches can have stupid mistakes you would rather rlm@11: clean up before publishing, and things that have not been rlm@11: merged into other branches can be easily rebased without rlm@11: affecting other people. But once it is published, you would rlm@11: not want to rewind it. rlm@11: rlm@11: (2) ... if a topic branch has been fully merged to "master". rlm@11: Then you can delete it. More importantly, you should not rlm@11: build on top of it -- other people may already want to rlm@11: change things related to the topic as patches against your rlm@11: "master", so if you need further changes, it is better to rlm@11: fork the topic (perhaps with the same name) afresh from the rlm@11: tip of "master". rlm@11: rlm@11: Let's look at this example: rlm@11: rlm@11: o---o---o---o---o---o---o---o---o---o "next" rlm@11: / / / / rlm@11: / a---a---b A / / rlm@11: / / / / rlm@11: / / c---c---c---c B / rlm@11: / / / \ / rlm@11: / / / b---b C \ / rlm@11: / / / / \ / rlm@11: ---o---o---o---o---o---o---o---o---o---o---o "master" rlm@11: rlm@11: rlm@11: A, B and C are topic branches. rlm@11: rlm@11: * A has one fix since it was merged up to "next". rlm@11: rlm@11: * B has finished. It has been fully merged up to "master" and "next", rlm@11: and is ready to be deleted. rlm@11: rlm@11: * C has not merged to "next" at all. rlm@11: rlm@11: We would want to allow C to be rebased, refuse A, and encourage rlm@11: B to be deleted. rlm@11: rlm@11: To compute (1): rlm@11: rlm@11: git-rev-list ^master ^topic next rlm@11: git-rev-list ^master next rlm@11: rlm@11: if these match, topic has not merged in next at all. rlm@11: rlm@11: To compute (2): rlm@11: rlm@11: git-rev-list master..topic rlm@11: rlm@11: if this is empty, it is fully merged to "master".