2 # Copyright (c) 2008 Benoit Sigoure <tsuna@lrde.epita.fr>
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation, either version 3 of the License, or
7 # (at your option) any later version.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
18 svn-merge2git [options] [refspec]
20 a,all Do the work on all local the branches
21 n,dry-run Do the entire processing without actually changing anything
22 r,remote Include remote branches (create a local branch for each of them)
23 v,verbose Be more verbose
29 # BRE (Basic RegExp) compatible with `git rev-list --grep' and `sed'. The RE
30 # *must* capture the revision merged in its first group.
31 merge_pattern
='[Mm]erge.*[0-9][0-9]*:\([0-9][0-9]*\)'
33 # BRE which is used to exclude matches of $merge_pattern
34 exclude_pattern
='Finish'
40 # print <msg> on stderr and exit 1
48 # Print <msg> when verbose mode is enabled.
51 $opt_verbose && echo "$*"
54 # find_merge_parent <ref> <merge-line>
55 # ------------------------------------
56 # Return (in $merge_parent) the sha1 of the commit that has been merged in by
57 # <ref>. <merge-line> must be a line extracted from the commit message of
58 # <ref> and will be used to extract the SVN revision merged. For instance, if
59 # <ref> is a SVN merge of merge-line='Merge -r42:51 in branch foo', this
60 # function puts the sha1 of r51 in $merge_parent.
63 # Find the first line that matches $merge_pattern, do the substitution and
64 # quit. Ignore all the other lines.
65 sed_tmp
="s/.*$merge_pattern.*/\\1/"
66 svn_merge_parent
=`echo "$2" | sed "$sed_tmp"`
67 case $svn_merge_parent in #(
69 fatal
"invalid SVN revision '$svn_merge_parent' found in $1";;
71 verb
" $1 is merging SVN r$svn_merge_parent"
72 merge_parent
=`git rev-list \
74 --grep="^ *git-svn-id: .*@$svn_merge_parent [-0-9a-f]*\\$"`
77 # create_graft <ref> <merge-parent>
78 # ---------------------------------
79 # Add <merge-parent> as 2nd parent of the commit designated by <ref>.
82 # --parents will print $1 along with its current parents.
83 grafted_commit
=`git rev-list -1 --parents "$1"`
85 test $rv -eq 0 || fatal
"git rev-list returned $rv"
88 case $grafted_commit in #(
89 *"$graft_merge_parent"*)
90 #verb " not grafting commit $1: $graft_merge_parent is already a parent ($grafted_commit)"
94 #verb " grafting commit $1: add parent $graft_merge_parent"
95 $opt_dryrun && return 0
96 echo >>"$GIT_DIR/info/grafts" \
97 "$grafted_commit $graft_merge_parent" \
98 || fatal
"Failed to add a graft in $GIT_DIR/info/grafts"
103 # Make *all* the grafts part of the actual history.
106 $opt_dryrun && return 0
107 git filter-branch
--parent-filter cat -- --all
112 # Find all the merge mentionned in the commit messages and make them become
117 verb
" >> Processing merges in the history of $refspec"
119 git rev-list
-1 "$refspec" >/dev
/null \
120 || fatal
"'$refspec' does not seem to be a valid refspec"
122 git rev-list
--grep="$merge_pattern" "$refspec" \
123 |
while read commit
; do
124 merge_line
=`git log -1 "$commit" | sed "/$merge_pattern/!d;//q"`
126 && echo "$merge_line" |
grep -- "$exclude_pattern" >/dev
/null
; then
127 verb
" skipping $commit whose log is: $merge_line"
130 verb
" $commit is a merge commit, log says:
133 find_merge_parent
"$commit" "$merge_line"
135 test $rv -eq 0 ||
exit $rv
137 case $merge_parent in #(
139 fatal
"invalid merge_parent: '$merge_parent'";;
142 create_graft
"$commit" "$merge_parent"
145 test $rv -eq 0 ||
exit $rv
148 # ------------------ #
149 # `main' starts here #
150 # ------------------ #
152 # Parse the options passed to the script.
153 # Initialize the defaults
179 for line
in `git for-each-ref --shell --format='ref=%(refname)' refs/heads`
187 for line
in `git for-each-ref --shell --format='ref=%(refname)' refs/remotes`
190 branch
=`basename "$ref"`
192 HEAD
) # Skip branches named `HEAD' (which does happen)
193 continue;; # because they create ambiguities.
195 # if the local $branch does not already exist, we create one
196 exists
=`git rev-list -1 refs/heads/"$branch" 2>/dev/null`
197 if test -z "$exists"; then # the $branch does not locally exist
198 verb
"creating branch '$branch' from '$ref'"
199 git branch
"$branch" "$ref" \
200 || fatal
"could not create branch '$branch' from '$ref'"
201 else # there already is a local $branch
202 sha1
=`git rev-list -1 "$ref"`
203 # Maybe the existing local $branch is identical to the remote $ref?
204 if test "$sha1" = "$exists"; then # OK, local = remote
205 verb
"branch '$branch' is already properly initialized to '$ref'"
206 set "$@" "refs/heads/$branch"
207 else # KO, local != remote
209 $me: warning: there already exists a local branch '$branch'
210 and it is at $exists whereas the remote branch '$ref'
211 is at $sha1 so I'm skipping it...
218 # No refspec given => work on HEAD
219 test -z "$*" && set HEAD
221 if test -z "$exclude_pattern"; then
230 echo ">>> processed $refspec"