##// END OF EJS Templates
convert: add bookmarks reading support to git backend
Edouard Gomez -
r13756:6b7077df default
parent child Browse files
Show More
@@ -1,170 +1,205 b''
1 1 # git.py - git support for the convert extension
2 2 #
3 3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 import os
9 9 from mercurial import util
10 10 from mercurial.node import hex, nullid
11 11 from mercurial.i18n import _
12 12
13 13 from common import NoRepo, commit, converter_source, checktool
14 14
15 15 class convert_git(converter_source):
16 16 # Windows does not support GIT_DIR= construct while other systems
17 17 # cannot remove environment variable. Just assume none have
18 18 # both issues.
19 19 if hasattr(os, 'unsetenv'):
20 def gitopen(self, s):
20 def gitopen(self, s, noerr=False):
21 21 prevgitdir = os.environ.get('GIT_DIR')
22 22 os.environ['GIT_DIR'] = self.path
23 23 try:
24 return util.popen(s, 'rb')
24 if noerr:
25 (stdin, stdout, stderr) = util.popen3(s)
26 return stdout
27 else:
28 return util.popen(s, 'rb')
25 29 finally:
26 30 if prevgitdir is None:
27 31 del os.environ['GIT_DIR']
28 32 else:
29 33 os.environ['GIT_DIR'] = prevgitdir
30 34 else:
31 def gitopen(self, s):
32 return util.popen('GIT_DIR=%s %s' % (self.path, s), 'rb')
35 def gitopen(self, s, noerr=False):
36 if noerr:
37 (sin, so, se) = util.popen3('GIT_DIR=%s %s' % (self.path, s))
38 return stdout
39 else:
40 util.popen('GIT_DIR=%s %s' % (self.path, s), 'rb')
33 41
34 42 def gitread(self, s):
35 43 fh = self.gitopen(s)
36 44 data = fh.read()
37 45 return data, fh.close()
38 46
39 47 def __init__(self, ui, path, rev=None):
40 48 super(convert_git, self).__init__(ui, path, rev=rev)
41 49
42 50 if os.path.isdir(path + "/.git"):
43 51 path += "/.git"
44 52 if not os.path.exists(path + "/objects"):
45 53 raise NoRepo(_("%s does not look like a Git repository") % path)
46 54
47 55 checktool('git', 'git')
48 56
49 57 self.path = path
50 58
51 59 def getheads(self):
52 60 if not self.rev:
53 61 heads, ret = self.gitread('git rev-parse --branches --remotes')
54 62 heads = heads.splitlines()
55 63 else:
56 64 heads, ret = self.gitread("git rev-parse --verify %s" % self.rev)
57 65 heads = [heads[:-1]]
58 66 if ret:
59 67 raise util.Abort(_('cannot retrieve git heads'))
60 68 return heads
61 69
62 70 def catfile(self, rev, type):
63 71 if rev == hex(nullid):
64 72 raise IOError()
65 73 data, ret = self.gitread("git cat-file %s %s" % (type, rev))
66 74 if ret:
67 75 raise util.Abort(_('cannot read %r object at %s') % (type, rev))
68 76 return data
69 77
70 78 def getfile(self, name, rev):
71 79 data = self.catfile(rev, "blob")
72 80 mode = self.modecache[(name, rev)]
73 81 return data, mode
74 82
75 83 def getchanges(self, version):
76 84 self.modecache = {}
77 85 fh = self.gitopen("git diff-tree -z --root -m -r %s" % version)
78 86 changes = []
79 87 seen = set()
80 88 entry = None
81 89 for l in fh.read().split('\x00'):
82 90 if not entry:
83 91 if not l.startswith(':'):
84 92 continue
85 93 entry = l
86 94 continue
87 95 f = l
88 96 if f not in seen:
89 97 seen.add(f)
90 98 entry = entry.split()
91 99 h = entry[3]
92 100 p = (entry[1] == "100755")
93 101 s = (entry[1] == "120000")
94 102 self.modecache[(f, h)] = (p and "x") or (s and "l") or ""
95 103 changes.append((f, h))
96 104 entry = None
97 105 if fh.close():
98 106 raise util.Abort(_('cannot read changes in %s') % version)
99 107 return (changes, {})
100 108
101 109 def getcommit(self, version):
102 110 c = self.catfile(version, "commit") # read the commit hash
103 111 end = c.find("\n\n")
104 112 message = c[end + 2:]
105 113 message = self.recode(message)
106 114 l = c[:end].splitlines()
107 115 parents = []
108 116 author = committer = None
109 117 for e in l[1:]:
110 118 n, v = e.split(" ", 1)
111 119 if n == "author":
112 120 p = v.split()
113 121 tm, tz = p[-2:]
114 122 author = " ".join(p[:-2])
115 123 if author[0] == "<": author = author[1:-1]
116 124 author = self.recode(author)
117 125 if n == "committer":
118 126 p = v.split()
119 127 tm, tz = p[-2:]
120 128 committer = " ".join(p[:-2])
121 129 if committer[0] == "<": committer = committer[1:-1]
122 130 committer = self.recode(committer)
123 131 if n == "parent":
124 132 parents.append(v)
125 133
126 134 if committer and committer != author:
127 135 message += "\ncommitter: %s\n" % committer
128 136 tzs, tzh, tzm = tz[-5:-4] + "1", tz[-4:-2], tz[-2:]
129 137 tz = -int(tzs) * (int(tzh) * 3600 + int(tzm))
130 138 date = tm + " " + str(tz)
131 139
132 140 c = commit(parents=parents, date=date, author=author, desc=message,
133 141 rev=version)
134 142 return c
135 143
136 144 def gettags(self):
137 145 tags = {}
138 146 fh = self.gitopen('git ls-remote --tags "%s"' % self.path)
139 147 prefix = 'refs/tags/'
140 148 for line in fh:
141 149 line = line.strip()
142 150 if not line.endswith("^{}"):
143 151 continue
144 152 node, tag = line.split(None, 1)
145 153 if not tag.startswith(prefix):
146 154 continue
147 155 tag = tag[len(prefix):-3]
148 156 tags[tag] = node
149 157 if fh.close():
150 158 raise util.Abort(_('cannot read tags from %s') % self.path)
151 159
152 160 return tags
153 161
154 162 def getchangedfiles(self, version, i):
155 163 changes = []
156 164 if i is None:
157 165 fh = self.gitopen("git diff-tree --root -m -r %s" % version)
158 166 for l in fh:
159 167 if "\t" not in l:
160 168 continue
161 169 m, f = l[:-1].split("\t")
162 170 changes.append(f)
163 171 else:
164 172 fh = self.gitopen('git diff-tree --name-only --root -r %s "%s^%s" --'
165 173 % (version, version, i + 1))
166 174 changes = [f.rstrip('\n') for f in fh]
167 175 if fh.close():
168 176 raise util.Abort(_('cannot read changes in %s') % version)
169 177
170 178 return changes
179
180 def getbookmarks(self):
181 bookmarks = {}
182
183 # Interesting references in git are prefixed
184 prefix = 'refs/heads/'
185 prefixlen = len(prefix)
186
187 # factor two commands
188 gitcmd = { 'remote/': 'git ls-remote --heads origin',
189 '': 'git show-ref'}
190
191 # Origin heads
192 for reftype in gitcmd:
193 try:
194 fh = self.gitopen(gitcmd[reftype], noerr=True)
195 for line in fh:
196 line = line.strip()
197 rev, name = line.split(None, 1)
198 if not name.startswith(prefix):
199 continue
200 name = '%s%s' % (reftype, name[prefixlen:])
201 bookmarks[name] = rev
202 except:
203 pass
204
205 return bookmarks
@@ -1,292 +1,297 b''
1 1
2 2 $ "$TESTDIR/hghave" git || exit 80
3 3 $ echo "[extensions]" >> $HGRCPATH
4 4 $ echo "convert=" >> $HGRCPATH
5 5 $ echo 'hgext.graphlog =' >> $HGRCPATH
6 6 $ GIT_AUTHOR_NAME='test'; export GIT_AUTHOR_NAME
7 7 $ GIT_AUTHOR_EMAIL='test@example.org'; export GIT_AUTHOR_EMAIL
8 8 $ GIT_AUTHOR_DATE="2007-01-01 00:00:00 +0000"; export GIT_AUTHOR_DATE
9 9 $ GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME"; export GIT_COMMITTER_NAME
10 10 $ GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL"; export GIT_COMMITTER_EMAIL
11 11 $ GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"; export GIT_COMMITTER_DATE
12 12 $ count=10
13 13 $ commit()
14 14 > {
15 15 > GIT_AUTHOR_DATE="2007-01-01 00:00:$count +0000"
16 16 > GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"
17 17 > git commit "$@" >/dev/null 2>/dev/null || echo "git commit error"
18 18 > count=`expr $count + 1`
19 19 > }
20 20 $ mkdir git-repo
21 21 $ cd git-repo
22 22 $ git init-db >/dev/null 2>/dev/null
23 23 $ echo a > a
24 24 $ mkdir d
25 25 $ echo b > d/b
26 26 $ git add a d
27 27 $ commit -a -m t1
28 28
29 29 Remove the directory, then try to replace it with a file
30 30 (issue 754)
31 31
32 32 $ git rm -f d/b
33 33 rm 'd/b'
34 34 $ commit -m t2
35 35 $ echo d > d
36 36 $ git add d
37 37 $ commit -m t3
38 38 $ echo b >> a
39 39 $ commit -a -m t4.1
40 40 $ git checkout -b other HEAD~ >/dev/null 2>/dev/null
41 41 $ echo c > a
42 42 $ echo a >> a
43 43 $ commit -a -m t4.2
44 44 $ git checkout master >/dev/null 2>/dev/null
45 45 $ git pull --no-commit . other > /dev/null 2>/dev/null
46 46 $ commit -m 'Merge branch other'
47 47 $ cd ..
48 48 $ hg convert --datesort git-repo
49 49 assuming destination git-repo-hg
50 50 initializing destination git-repo-hg repository
51 51 scanning source...
52 52 sorting...
53 53 converting...
54 54 5 t1
55 55 4 t2
56 56 3 t3
57 57 2 t4.1
58 58 1 t4.2
59 59 0 Merge branch other
60 updating bookmarks
60 61 $ hg up -q -R git-repo-hg
61 62 $ hg -R git-repo-hg tip -v
62 63 changeset: 5:c78094926be2
64 bookmark: master
63 65 tag: tip
64 66 parent: 3:f5f5cb45432b
65 67 parent: 4:4e174f80c67c
66 68 user: test <test@example.org>
67 69 date: Mon Jan 01 00:00:15 2007 +0000
68 70 files: a
69 71 description:
70 72 Merge branch other
71 73
72 74
73 75 $ count=10
74 76 $ mkdir git-repo2
75 77 $ cd git-repo2
76 78 $ git init-db >/dev/null 2>/dev/null
77 79 $ echo foo > foo
78 80 $ git add foo
79 81 $ commit -a -m 'add foo'
80 82 $ echo >> foo
81 83 $ commit -a -m 'change foo'
82 84 $ git checkout -b Bar HEAD~ >/dev/null 2>/dev/null
83 85 $ echo quux >> quux
84 86 $ git add quux
85 87 $ commit -a -m 'add quux'
86 88 $ echo bar > bar
87 89 $ git add bar
88 90 $ commit -a -m 'add bar'
89 91 $ git checkout -b Baz HEAD~ >/dev/null 2>/dev/null
90 92 $ echo baz > baz
91 93 $ git add baz
92 94 $ commit -a -m 'add baz'
93 95 $ git checkout master >/dev/null 2>/dev/null
94 96 $ git pull --no-commit . Bar Baz > /dev/null 2>/dev/null
95 97 $ commit -m 'Octopus merge'
96 98 $ echo bar >> bar
97 99 $ commit -a -m 'change bar'
98 100 $ git checkout -b Foo HEAD~ >/dev/null 2>/dev/null
99 101 $ echo >> foo
100 102 $ commit -a -m 'change foo'
101 103 $ git checkout master >/dev/null 2>/dev/null
102 104 $ git pull --no-commit -s ours . Foo > /dev/null 2>/dev/null
103 105 $ commit -m 'Discard change to foo'
104 106 $ cd ..
105 107 $ glog()
106 108 > {
107 109 > hg glog --template '{rev} "{desc|firstline}" files: {files}\n' "$@"
108 110 > }
109 111 $ splitrepo()
110 112 > {
111 113 > msg="$1"
112 114 > files="$2"
113 115 > opts=$3
114 116 > echo "% $files: $msg"
115 117 > prefix=`echo "$files" | sed -e 's/ /-/g'`
116 118 > fmap="$prefix.fmap"
117 119 > repo="$prefix.repo"
118 120 > for i in $files; do
119 121 > echo "include $i" >> "$fmap"
120 122 > done
121 123 > hg -q convert $opts --filemap "$fmap" --datesort git-repo2 "$repo"
122 124 > hg up -q -R "$repo"
123 125 > glog -R "$repo"
124 126 > hg -R "$repo" manifest --debug
125 127 > }
126 128
127 129 full conversion
128 130
129 131 $ hg -q convert --datesort git-repo2 fullrepo
130 132 $ hg up -q -R fullrepo
131 133 $ glog -R fullrepo
132 134 @ 9 "Discard change to foo" files: foo
133 135 |\
134 136 | o 8 "change foo" files: foo
135 137 | |
136 138 o | 7 "change bar" files: bar
137 139 |/
138 140 o 6 "(octopus merge fixup)" files:
139 141 |\
140 142 | o 5 "Octopus merge" files: baz
141 143 | |\
142 144 o | | 4 "add baz" files: baz
143 145 | | |
144 146 +---o 3 "add bar" files: bar
145 147 | |
146 148 o | 2 "add quux" files: quux
147 149 | |
148 150 | o 1 "change foo" files: foo
149 151 |/
150 152 o 0 "add foo" files: foo
151 153
152 154 $ hg -R fullrepo manifest --debug
153 155 245a3b8bc653999c2b22cdabd517ccb47aecafdf 644 bar
154 156 354ae8da6e890359ef49ade27b68bbc361f3ca88 644 baz
155 157 9277c9cc8dd4576fc01a17939b4351e5ada93466 644 foo
156 158 88dfeab657e8cf2cef3dec67b914f49791ae76b1 644 quux
157 159 $ splitrepo 'octopus merge' 'foo bar baz'
158 160 % foo bar baz: octopus merge
159 161 @ 8 "Discard change to foo" files: foo
160 162 |\
161 163 | o 7 "change foo" files: foo
162 164 | |
163 165 o | 6 "change bar" files: bar
164 166 |/
165 167 o 5 "(octopus merge fixup)" files:
166 168 |\
167 169 | o 4 "Octopus merge" files: baz
168 170 | |\
169 171 o | | 3 "add baz" files: baz
170 172 | | |
171 173 +---o 2 "add bar" files: bar
172 174 | |
173 175 | o 1 "change foo" files: foo
174 176 |/
175 177 o 0 "add foo" files: foo
176 178
177 179 245a3b8bc653999c2b22cdabd517ccb47aecafdf 644 bar
178 180 354ae8da6e890359ef49ade27b68bbc361f3ca88 644 baz
179 181 9277c9cc8dd4576fc01a17939b4351e5ada93466 644 foo
180 182 $ splitrepo 'only some parents of an octopus merge; "discard" a head' 'foo baz quux'
181 183 % foo baz quux: only some parents of an octopus merge; "discard" a head
182 184 @ 6 "Discard change to foo" files: foo
183 185 |
184 186 o 5 "change foo" files: foo
185 187 |
186 188 o 4 "Octopus merge" files:
187 189 |\
188 190 | o 3 "add baz" files: baz
189 191 | |
190 192 | o 2 "add quux" files: quux
191 193 | |
192 194 o | 1 "change foo" files: foo
193 195 |/
194 196 o 0 "add foo" files: foo
195 197
196 198 354ae8da6e890359ef49ade27b68bbc361f3ca88 644 baz
197 199 9277c9cc8dd4576fc01a17939b4351e5ada93466 644 foo
198 200 88dfeab657e8cf2cef3dec67b914f49791ae76b1 644 quux
199 201 $ echo
200 202
201 203
202 204 test binary conversion (issue 1359)
203 205
204 206 $ mkdir git-repo3
205 207 $ cd git-repo3
206 208 $ git init-db >/dev/null 2>/dev/null
207 209 $ python -c 'file("b", "wb").write("".join([chr(i) for i in range(256)])*16)'
208 210 $ git add b
209 211 $ commit -a -m addbinary
210 212 $ cd ..
211 213
212 214 convert binary file
213 215
214 216 $ hg convert git-repo3 git-repo3-hg
215 217 initializing destination git-repo3-hg repository
216 218 scanning source...
217 219 sorting...
218 220 converting...
219 221 0 addbinary
222 updating bookmarks
220 223 $ cd git-repo3-hg
221 224 $ hg up -C
222 225 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
223 226 $ python -c 'print len(file("b", "rb").read())'
224 227 4096
225 228 $ cd ..
226 229 $ echo
227 230
228 231
229 232 test author vs committer
230 233
231 234 $ mkdir git-repo4
232 235 $ cd git-repo4
233 236 $ git init-db >/dev/null 2>/dev/null
234 237 $ echo >> foo
235 238 $ git add foo
236 239 $ commit -a -m addfoo
237 240 $ echo >> foo
238 241 $ GIT_AUTHOR_NAME="nottest"
239 242 $ commit -a -m addfoo2
240 243 $ cd ..
241 244
242 245 convert author committer
243 246
244 247 $ hg convert git-repo4 git-repo4-hg
245 248 initializing destination git-repo4-hg repository
246 249 scanning source...
247 250 sorting...
248 251 converting...
249 252 1 addfoo
250 253 0 addfoo2
254 updating bookmarks
251 255 $ hg -R git-repo4-hg log -v
252 256 changeset: 1:d63e967f93da
257 bookmark: master
253 258 tag: tip
254 259 user: nottest <test@example.org>
255 260 date: Mon Jan 01 00:00:21 2007 +0000
256 261 files: foo
257 262 description:
258 263 addfoo2
259 264
260 265 committer: test <test@example.org>
261 266
262 267
263 268 changeset: 0:0735477b0224
264 269 user: test <test@example.org>
265 270 date: Mon Jan 01 00:00:20 2007 +0000
266 271 files: foo
267 272 description:
268 273 addfoo
269 274
270 275
271 276
272 277 --sourceorder should fail
273 278
274 279 $ hg convert --sourcesort git-repo4 git-repo4-sourcesort-hg
275 280 initializing destination git-repo4-sourcesort-hg repository
276 281 abort: --sourcesort is not supported by this data source
277 282 [255]
278 283
279 284 damage git repository and convert again
280 285
281 286 $ cat > damage.py <<EOF
282 287 > import os
283 288 > for root, dirs, files in os.walk('git-repo4/.git/objects'):
284 289 > if files:
285 290 > path = os.path.join(root, files[0])
286 291 > os.remove(path)
287 292 > break
288 293 > EOF
289 294 $ python damage.py
290 295 $ hg convert git-repo4 git-repo4-broken-hg 2>&1 | \
291 296 > grep 'abort:' | sed 's/abort:.*/abort:/g'
292 297 abort:
@@ -1,82 +1,84 b''
1 1
2 2 $ "$TESTDIR/hghave" git || exit 80
3 3 $ echo "[extensions]" >> $HGRCPATH
4 4 $ echo "convert=" >> $HGRCPATH
5 5 $ echo 'hgext.graphlog =' >> $HGRCPATH
6 6 $ echo '[convert]' >> $HGRCPATH
7 7 $ echo 'hg.usebranchnames = True' >> $HGRCPATH
8 8 $ echo 'hg.tagsbranch = tags-update' >> $HGRCPATH
9 9 $ GIT_AUTHOR_NAME='test'; export GIT_AUTHOR_NAME
10 10 $ GIT_AUTHOR_EMAIL='test@example.org'; export GIT_AUTHOR_EMAIL
11 11 $ GIT_AUTHOR_DATE="2007-01-01 00:00:00 +0000"; export GIT_AUTHOR_DATE
12 12 $ GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME"; export GIT_COMMITTER_NAME
13 13 $ GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL"; export GIT_COMMITTER_EMAIL
14 14 $ GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"; export GIT_COMMITTER_DATE
15 15 $ count=10
16 16 $ action()
17 17 > {
18 18 > GIT_AUTHOR_DATE="2007-01-01 00:00:$count +0000"
19 19 > GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"
20 20 > git "$@" >/dev/null 2>/dev/null || echo "git command error"
21 21 > count=`expr $count + 1`
22 22 > }
23 23 $ glog()
24 24 > {
25 25 > hg glog --template '{rev} "{desc|firstline}" files: {files}\n' "$@"
26 26 > }
27 27 $ convertrepo()
28 28 > {
29 29 > hg convert --datesort git-repo hg-repo
30 30 > }
31 31
32 32 Build a GIT repo with at least 1 tag
33 33
34 34 $ mkdir git-repo
35 35 $ cd git-repo
36 36 $ git init >/dev/null 2>&1
37 37 $ echo a > a
38 38 $ git add a
39 39 $ action commit -m "rev1"
40 40 $ action tag -m "tag1" tag1
41 41 $ cd ..
42 42
43 43 Do a first conversion
44 44
45 45 $ convertrepo
46 46 initializing destination hg-repo repository
47 47 scanning source...
48 48 sorting...
49 49 converting...
50 50 0 rev1
51 51 updating tags
52 updating bookmarks
52 53
53 54 Simulate upstream updates after first conversion
54 55
55 56 $ cd git-repo
56 57 $ echo b > a
57 58 $ git add a
58 59 $ action commit -m "rev2"
59 60 $ action tag -m "tag2" tag2
60 61 $ cd ..
61 62
62 63 Perform an incremental conversion
63 64
64 65 $ convertrepo
65 66 scanning source...
66 67 sorting...
67 68 converting...
68 69 0 rev2
69 70 updating tags
71 updating bookmarks
70 72
71 73 Print the log
72 74
73 75 $ cd hg-repo
74 76 $ glog
75 77 o 3 "update tags" files: .hgtags
76 78 |
77 79 | o 2 "rev2" files: a
78 80 | |
79 81 o | 1 "update tags" files: .hgtags
80 82 /
81 83 o 0 "rev1" files: a
82 84
General Comments 0
You need to be logged in to leave comments. Login now