Initial version.
[svn-merge2git.git] / svn-merge2git.sh
blobd1d6859655d3d6ae9686e0593dca7ca3fec27250
1 #!/bin/sh
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/>.
17 OPTIONS_SPEC="\
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
25 SUBDIRECTORY_OK=Yes
26 . git-sh-setup
27 cd_to_toplevel
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'
36 me=`basename "$0"`
38 # fatal <msg>
39 # -----------
40 # print <msg> on stderr and exit 1
41 fatal()
43 die "$me: error: $*"
46 # verb <msg>
47 # ----------
48 # Print <msg> when verbose mode is enabled.
49 verb()
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.
61 find_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 #(
68 '' | *[^0-9]*)
69 fatal "invalid SVN revision '$svn_merge_parent' found in $1";;
70 esac
71 verb " $1 is merging SVN r$svn_merge_parent"
72 merge_parent=`git rev-list \
73 --all \
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>.
80 create_graft()
82 # --parents will print $1 along with its current parents.
83 grafted_commit=`git rev-list -1 --parents "$1"`
84 rv=$?
85 test $rv -eq 0 || fatal "git rev-list returned $rv"
86 graft_merge_parent=$2
88 case $grafted_commit in #(
89 *"$graft_merge_parent"*)
90 #verb " not grafting commit $1: $graft_merge_parent is already a parent ($grafted_commit)"
91 return 0;;
92 esac
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"
101 # rewrite_history
102 # ---------------
103 # Make *all* the grafts part of the actual history.
104 rewrite_history()
106 $opt_dryrun && return 0
107 git filter-branch --parent-filter cat -- --all
110 # doit <REF>
111 # ----------
112 # Find all the merge mentionned in the commit messages and make them become
113 # real Git merges.
114 doit()
116 refspec=$1
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"`
125 if $has_exclude \
126 && echo "$merge_line" | grep -- "$exclude_pattern" >/dev/null; then
127 verb " skipping $commit whose log is: $merge_line"
128 continue
130 verb " $commit is a merge commit, log says:
131 | $merge_line"
133 find_merge_parent "$commit" "$merge_line"
134 rv=$?
135 test $rv -eq 0 || exit $rv
137 case $merge_parent in #(
138 '' | *[^0-9a-f]*)
139 fatal "invalid merge_parent: '$merge_parent'";;
140 esac
142 create_graft "$commit" "$merge_parent"
143 done
144 rv=$?
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
154 opt_all=false
155 opt_dryrun=false
156 opt_remote=false
157 opt_verbose=false
159 while test $# != 0
161 case $1 in #(
162 -a | --all)
163 opt_all=:;; #(
164 -n | --dry-run)
165 opt_dryrun=:;; #(
166 -r | --remote)
167 opt_remote=:;; #(
168 -v | --verbose)
169 opt_verbose=:;; #(
171 shift; break;; #(
173 usage;; #(
174 esac
175 shift
176 done
178 if $opt_all; then
179 for line in `git for-each-ref --shell --format='ref=%(refname)' refs/heads`
181 eval "$line"
182 set "$@" "$ref"
183 done
186 if $opt_remote; then
187 for line in `git for-each-ref --shell --format='ref=%(refname)' refs/remotes`
189 eval "$line"
190 branch=`basename "$ref"`
191 case $branch in #(
192 HEAD) # Skip branches named `HEAD' (which does happen)
193 continue;; # because they create ambiguities.
194 esac
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
208 cat <<EOF
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...
215 done
218 # No refspec given => work on HEAD
219 test -z "$*" && set HEAD
221 if test -z "$exclude_pattern"; then
222 has_exclude=false
223 else
224 has_exclude=:
227 for refspec
229 doit "$refspec"
230 echo ">>> processed $refspec"
231 done
233 rewrite_history