#!/usr/bin/env bash # # Merge a branch to the current tree. # Copyright (c) Petr Baudis, 2005 # # Takes a parameter identifying the branch to be merged, defaulting # to 'origin'. # # This command merges all changes currently in the given branch to your # current branch. This can produce a merging commit on your branch sticking # the two branch together (so-called 'tree merge'). However in case there # are no changes in your branch that wouldn't be in the remote branch, no # merge commit is done and commit pointer of your branch is just updated # to the last commit on the remote branch (so-called 'fast-forward merge'). # # In case of conflicts being generated by the merge, you have to examine # the tree (cg-merge will tell you which files contain commits; the commits # are denoted by rcsmerge-like markers <<<<, ====, and >>>>) and then do # `cg-commit` yourself. `cg-commit` will know that you are committing a merge # and will record it properly. # # Note that when you are merging remote branches, `cg-merge` will use them # in the state they are currently at in your repository. If you want to # fetch the latest changes from the remote repository, use `cg-fetch`. If you # want to fetch the changes and then merge them to your branch, use the command # `cg-update`. # # Also note that if you have local changes in your tree that you did not # commit, cg-merge will always preserve them when fast-forwarding. When doing # a tree merge, it will preserve them if they don't conflict with the merged # changes, and report an error otherwise. In short, it should do the Right # Thing (tm), never lose your local changes and never let them mix up with # the merge. # # OPTIONS # ------- # -b BASE_COMMIT:: Specify the base commit for the merge # Parameter specifies the base commit for the merge. Otherwise, the # least common ancestor is automatically selected. # # -c:: Disable autocommitting # Parameter specifies that you want to have tree merge never # autocommitted, but want to review and commit it manually. This will # basically make cg-merge always behave like there were conflicts # during the merge. # # -j:: Join current branch with BRANCH_NAME # Join the current branch and BRANCH_NAME together. This makes sense # when the branches have no common history, meaning they are actually # not branches related at all as far as GIT is concerned. Merging such # branches might be a user error and you well may be doing something # you do not want; but equally likely, you may actually WANT to join # the projects together, which is what this option does. # # --squash:: Use "squash" merge to record pending commits as a single merge commit # "Squash" merge - condense all the to-be-merged commits to a single # merge commit. This means "throw away history of the branch I'm # merging", essentially like in CVS or SVN, with the same problem - # re-merging with that branch later will cause trouble. This is not # recommended unless you actually really want to flatten the history # of the merged branch, e.g. when merging topical branches to your # mainline (you want to have the logical change you developed in # a branch as a single "do it" commit instead of a sequence of # "do it I", "fix it", "do it II", "fix it II", "fix it III" commits # like you would get with a regular merge). # # -v:: Enable verbosity # Display more verbose output - most notably list all the files # touched by the merged changes. # # HOOKS # ----- # '.git/hooks/merge-pre' BRANCH BASE CURHEAD MERGEDHEAD MERGETYPE:: # If the file exists and is executable it will be executed right # before the merge itself happens. The merge is cancelled if the script # returns non-zero exit code. # - MERGETYPE is either "forward", "squash", or "tree". # # '.git/hooks/merge-post' BRANCH BASE CURHEAD MERGEDHEAD MERGETYPE STATUS:: # If the file exists and is executable it will be executed after # the merge is done. # - MERGETYPE is either "forward", "squash", or "tree". # - For 'forward', the STATUS is always "ok", while for "squash" # and "tree" the STATUS can be "localchanges", "conflicts", # "nocommit", or "ok". # Developer's documentation: # # ENVIRONMENT # ----------- # _cg_orig_head:: # The original commit ID of the to-be-merged branch, if cg-merge # is called right after fetch. This is used to do better decision # about whether to fast-forward or tree-merge. USAGE="cg-merge [-c] [-b BASE_COMMIT] [-j] [--squash] [-v] [BRANCH_NAME]" _git_requires_root=1 . "${COGITO_LIB}"cg-Xlib || exit 1 prehook() { if [ -x "$_git/hooks/merge-pre" ]; then "$_git/hooks/merge-pre" "$branchname" "$base" "$head" "$branch" "$@" || die "merge cancelled by hook" fi } posthook() { if [ -x "$_git/hooks/merge-post" ]; then "$_git/hooks/merge-post" "$branchname" "$base" "$head" "$branch" "$@" fi } head="$(cg-object-id -c)" || exit 1 careful= base= join= squash= verbose= while optparse; do if optparse -c; then careful=1 elif optparse -b=; then base="$(cg-object-id -c "$OPTARG")" || exit 1 elif optparse -j; then join=1 # -s reserved to strategy elif optparse --squash; then squash=1 elif optparse -v; then verbose=1 else optfail fi done branchname="${ARGS[0]}" [ "$branchname" ] || { [ -s "$_git/branches/origin" ] && branchname=origin; } [ "$branchname" ] || die "what to merge?" branch=$(cg-object-id -c "$branchname") || exit 1 [ "$base" ] || base="$(git-merge-base --all "$head" "$branch")" if [ ! "$join" ]; then [ "$base" ] || die "unable to automatically determine merge base (consider cg-merge -j)" baselist=($base) if [ "${#baselist[@]}" -gt "1" ]; then echo "Multiple merge base candidates, please select one manually (by running cg-merge -b BASE [BRANCH]):" echo "${baselist[*]}" | tr ' ' '\n' echo conservative_merge_base "${baselist[@]}" # -> _cg_baselist echo -n "The most conservative base (but likely a lot of conflicts): " echo "${_cg_baselist[*]}" exit 3 fi >&2 else [ "$base" ] && die "joining branches with common history is something I refuse to do" index="$(mktemp -t gitmerge.XXXXXX)" || exit $? GIT_INDEX_FILE="$index" git-read-tree base="$(GIT_INDEX_FILE="$index" git-write-tree)" rm "$index" fi [ -s "$_git/blocked" ] && die "merge blocked: $(cat "$_git/blocked")" if [ -s "$_git/merging" ] && grep -q "$branch" "$_git/merging"; then echo "Branch already merged in the working tree." >&2 exit 0 fi if [ "$base" = "$branch" ]; then echo "Branch already fully merged." >&2 exit 0 fi if { [ "$head" = "$base" ] || [ "$head" = "$_cg_orig_head" ]; } && [ ! "$squash" ] && [ ! -s "$_git/merging" ]; then # No need to do explicit merge with a merge commit; just bring # the HEAD forward. echo "Fast-forwarding $base -> $branch" >&2 echo -e "\ton top of $head ..." >&2 [ "$verbose" ] && git-diff-tree --abbrev -r "$(cg-object-id -t "$base")" "$(cg-object-id -t "$branch")" prehook forward tree_timewarp "forward" "yes, rollback (or rather rollforth) the tree!" "$base" "$branch" posthook forward ok exit 0 fi [ "$(git-diff-files -s)" ] && git-update-index --refresh >/dev/null if [ ! "$squash" ]; then [ -s "$_git/squashing" ] && die "cannot combine squashing and non-squashing merges" echo "Merging $base -> $branch" >&2 echo -e "\tto $head ..." >&2 mergetype="tree" else echo "Squashing $base -> $branch" >&2 echo -e "\ton top of $head ..." >&2 mergetype="squash" fi [ "$verbose" ] && git-diff-tree --abbrev -r "$(cg-object-id -t "$base")" "$(cg-object-id -t "$branch")" prehook "$mergetype" git-diff-index --name-only "$(cg-object-id -t $head)" >>"$_git/commit-ignore" # Don't keep around useless empty files [ -s "$_git/commit-ignore" ] || rm "$_git/commit-ignore" if ! git-read-tree -u -m "$(cg-object-id -t "$base")" "$(cg-object-id -t "$head")" "$(cg-object-id -t "$branch")"; then echo "cg-merge: git-read-tree failed (merge likely blocked by local changes)" >&2 posthook "$mergetype" localchanges rm -f "$_git/commit-ignore" exit 1 fi echo "$base" >>"$_git/merge-base" echo "$branch" >>"$_git/merging" echo "$branchname" >>"$_git/merging-sym" [ "$squash" ] && echo "$branch" >>"$_git/squashing" if ! git-merge-index -o -q ""${COGITO_LIB}"cg-Xmergefile" -a || [ "$careful" ]; then echo >&2 if [ ! "$careful" ]; then echo " Conflicts during merge. Do cg-commit after resolving them." >&2 else echo " Do cg-commit after reviewing the merge." >&2 fi if [ -s "$_git/commit-ignore" ]; then echo " cg-reset will cancel the merge (but also your pending local changes!)." >&2 echo >&2 echo " These files contained local modifications and won't be automatically chosen for committing:" >&2 cat "$_git/commit-ignore" >&2 else echo " cg-reset will cancel the merge." >&2 echo >&2 fi posthook "$mergetype" conflicts exit 2 fi echo readtree= if ! cg-commit -C; then readtree=1 echo "cg-merge: COMMIT FAILED, retry manually" >&2 if [ -s "$_git/commit-ignore" ]; then echo " cg-reset will cancel the merge (but also your pending local changes!)." >&2 else echo " cg-reset will cancel the merge." >&2 fi posthook "$mergetype" nocommit fi [ "$readtree" ] && git-read-tree -m HEAD # update_index here is safe because no tree<->index desyncs could've # survived the read-tree above update_index posthook "$mergetype" ok