##// END OF EJS Templates
convert: use 'unknown' and '0 0' if commit author or date weren't specified...
Alexis S. L. Carvalho -
r5984:9451a941 default
parent child Browse files
Show More
@@ -1,185 +1,185 b''
1 # common code for the convert extension
1 # common code for the convert extension
2 import base64
2 import base64
3 import cPickle as pickle
3 import cPickle as pickle
4
4
5 def encodeargs(args):
5 def encodeargs(args):
6 def encodearg(s):
6 def encodearg(s):
7 lines = base64.encodestring(s)
7 lines = base64.encodestring(s)
8 lines = [l.splitlines()[0] for l in lines]
8 lines = [l.splitlines()[0] for l in lines]
9 return ''.join(lines)
9 return ''.join(lines)
10
10
11 s = pickle.dumps(args)
11 s = pickle.dumps(args)
12 return encodearg(s)
12 return encodearg(s)
13
13
14 def decodeargs(s):
14 def decodeargs(s):
15 s = base64.decodestring(s)
15 s = base64.decodestring(s)
16 return pickle.loads(s)
16 return pickle.loads(s)
17
17
18 class NoRepo(Exception): pass
18 class NoRepo(Exception): pass
19
19
20 SKIPREV = 'SKIP'
20 SKIPREV = 'SKIP'
21
21
22 class commit(object):
22 class commit(object):
23 def __init__(self, author, date, desc, parents, branch=None, rev=None,
23 def __init__(self, author, date, desc, parents, branch=None, rev=None,
24 extra={}):
24 extra={}):
25 self.author = author
25 self.author = author or 'unknown'
26 self.date = date
26 self.date = date or '0 0'
27 self.desc = desc
27 self.desc = desc
28 self.parents = parents
28 self.parents = parents
29 self.branch = branch
29 self.branch = branch
30 self.rev = rev
30 self.rev = rev
31 self.extra = extra
31 self.extra = extra
32
32
33 class converter_source(object):
33 class converter_source(object):
34 """Conversion source interface"""
34 """Conversion source interface"""
35
35
36 def __init__(self, ui, path, rev=None):
36 def __init__(self, ui, path, rev=None):
37 """Initialize conversion source (or raise NoRepo("message")
37 """Initialize conversion source (or raise NoRepo("message")
38 exception if path is not a valid repository)"""
38 exception if path is not a valid repository)"""
39 self.ui = ui
39 self.ui = ui
40 self.path = path
40 self.path = path
41 self.rev = rev
41 self.rev = rev
42
42
43 self.encoding = 'utf-8'
43 self.encoding = 'utf-8'
44
44
45 def before(self):
45 def before(self):
46 pass
46 pass
47
47
48 def after(self):
48 def after(self):
49 pass
49 pass
50
50
51 def setrevmap(self, revmap, order):
51 def setrevmap(self, revmap, order):
52 """set the map of already-converted revisions
52 """set the map of already-converted revisions
53
53
54 order is a list with the keys from revmap in the order they
54 order is a list with the keys from revmap in the order they
55 appear in the revision map file."""
55 appear in the revision map file."""
56 pass
56 pass
57
57
58 def getheads(self):
58 def getheads(self):
59 """Return a list of this repository's heads"""
59 """Return a list of this repository's heads"""
60 raise NotImplementedError()
60 raise NotImplementedError()
61
61
62 def getfile(self, name, rev):
62 def getfile(self, name, rev):
63 """Return file contents as a string"""
63 """Return file contents as a string"""
64 raise NotImplementedError()
64 raise NotImplementedError()
65
65
66 def getmode(self, name, rev):
66 def getmode(self, name, rev):
67 """Return file mode, eg. '', 'x', or 'l'"""
67 """Return file mode, eg. '', 'x', or 'l'"""
68 raise NotImplementedError()
68 raise NotImplementedError()
69
69
70 def getchanges(self, version):
70 def getchanges(self, version):
71 """Returns a tuple of (files, copies)
71 """Returns a tuple of (files, copies)
72 Files is a sorted list of (filename, id) tuples for all files changed
72 Files is a sorted list of (filename, id) tuples for all files changed
73 in version, where id is the source revision id of the file.
73 in version, where id is the source revision id of the file.
74
74
75 copies is a dictionary of dest: source
75 copies is a dictionary of dest: source
76 """
76 """
77 raise NotImplementedError()
77 raise NotImplementedError()
78
78
79 def getcommit(self, version):
79 def getcommit(self, version):
80 """Return the commit object for version"""
80 """Return the commit object for version"""
81 raise NotImplementedError()
81 raise NotImplementedError()
82
82
83 def gettags(self):
83 def gettags(self):
84 """Return the tags as a dictionary of name: revision"""
84 """Return the tags as a dictionary of name: revision"""
85 raise NotImplementedError()
85 raise NotImplementedError()
86
86
87 def recode(self, s, encoding=None):
87 def recode(self, s, encoding=None):
88 if not encoding:
88 if not encoding:
89 encoding = self.encoding or 'utf-8'
89 encoding = self.encoding or 'utf-8'
90
90
91 if isinstance(s, unicode):
91 if isinstance(s, unicode):
92 return s.encode("utf-8")
92 return s.encode("utf-8")
93 try:
93 try:
94 return s.decode(encoding).encode("utf-8")
94 return s.decode(encoding).encode("utf-8")
95 except:
95 except:
96 try:
96 try:
97 return s.decode("latin-1").encode("utf-8")
97 return s.decode("latin-1").encode("utf-8")
98 except:
98 except:
99 return s.decode(encoding, "replace").encode("utf-8")
99 return s.decode(encoding, "replace").encode("utf-8")
100
100
101 def getchangedfiles(self, rev, i):
101 def getchangedfiles(self, rev, i):
102 """Return the files changed by rev compared to parent[i].
102 """Return the files changed by rev compared to parent[i].
103
103
104 i is an index selecting one of the parents of rev. The return
104 i is an index selecting one of the parents of rev. The return
105 value should be the list of files that are different in rev and
105 value should be the list of files that are different in rev and
106 this parent.
106 this parent.
107
107
108 If rev has no parents, i is None.
108 If rev has no parents, i is None.
109
109
110 This function is only needed to support --filemap
110 This function is only needed to support --filemap
111 """
111 """
112 raise NotImplementedError()
112 raise NotImplementedError()
113
113
114 class converter_sink(object):
114 class converter_sink(object):
115 """Conversion sink (target) interface"""
115 """Conversion sink (target) interface"""
116
116
117 def __init__(self, ui, path):
117 def __init__(self, ui, path):
118 """Initialize conversion sink (or raise NoRepo("message")
118 """Initialize conversion sink (or raise NoRepo("message")
119 exception if path is not a valid repository)
119 exception if path is not a valid repository)
120
120
121 created is a list of paths to remove if a fatal error occurs
121 created is a list of paths to remove if a fatal error occurs
122 later"""
122 later"""
123 self.ui = ui
123 self.ui = ui
124 self.path = path
124 self.path = path
125 self.created = []
125 self.created = []
126
126
127 def getheads(self):
127 def getheads(self):
128 """Return a list of this repository's heads"""
128 """Return a list of this repository's heads"""
129 raise NotImplementedError()
129 raise NotImplementedError()
130
130
131 def revmapfile(self):
131 def revmapfile(self):
132 """Path to a file that will contain lines
132 """Path to a file that will contain lines
133 source_rev_id sink_rev_id
133 source_rev_id sink_rev_id
134 mapping equivalent revision identifiers for each system."""
134 mapping equivalent revision identifiers for each system."""
135 raise NotImplementedError()
135 raise NotImplementedError()
136
136
137 def authorfile(self):
137 def authorfile(self):
138 """Path to a file that will contain lines
138 """Path to a file that will contain lines
139 srcauthor=dstauthor
139 srcauthor=dstauthor
140 mapping equivalent authors identifiers for each system."""
140 mapping equivalent authors identifiers for each system."""
141 return None
141 return None
142
142
143 def putfile(self, f, e, data):
143 def putfile(self, f, e, data):
144 """Put file for next putcommit().
144 """Put file for next putcommit().
145 f: path to file
145 f: path to file
146 e: '', 'x', or 'l' (regular file, executable, or symlink)
146 e: '', 'x', or 'l' (regular file, executable, or symlink)
147 data: file contents"""
147 data: file contents"""
148 raise NotImplementedError()
148 raise NotImplementedError()
149
149
150 def delfile(self, f):
150 def delfile(self, f):
151 """Delete file for next putcommit().
151 """Delete file for next putcommit().
152 f: path to file"""
152 f: path to file"""
153 raise NotImplementedError()
153 raise NotImplementedError()
154
154
155 def putcommit(self, files, parents, commit):
155 def putcommit(self, files, parents, commit):
156 """Create a revision with all changed files listed in 'files'
156 """Create a revision with all changed files listed in 'files'
157 and having listed parents. 'commit' is a commit object containing
157 and having listed parents. 'commit' is a commit object containing
158 at a minimum the author, date, and message for this changeset.
158 at a minimum the author, date, and message for this changeset.
159 Called after putfile() and delfile() calls. Note that the sink
159 Called after putfile() and delfile() calls. Note that the sink
160 repository is not told to update itself to a particular revision
160 repository is not told to update itself to a particular revision
161 (or even what that revision would be) before it receives the
161 (or even what that revision would be) before it receives the
162 file data."""
162 file data."""
163 raise NotImplementedError()
163 raise NotImplementedError()
164
164
165 def puttags(self, tags):
165 def puttags(self, tags):
166 """Put tags into sink.
166 """Put tags into sink.
167 tags: {tagname: sink_rev_id, ...}"""
167 tags: {tagname: sink_rev_id, ...}"""
168 raise NotImplementedError()
168 raise NotImplementedError()
169
169
170 def setbranch(self, branch, pbranches):
170 def setbranch(self, branch, pbranches):
171 """Set the current branch name. Called before the first putfile
171 """Set the current branch name. Called before the first putfile
172 on the branch.
172 on the branch.
173 branch: branch name for subsequent commits
173 branch: branch name for subsequent commits
174 pbranches: (converted parent revision, parent branch) tuples"""
174 pbranches: (converted parent revision, parent branch) tuples"""
175 pass
175 pass
176
176
177 def setfilemapmode(self, active):
177 def setfilemapmode(self, active):
178 """Tell the destination that we're using a filemap
178 """Tell the destination that we're using a filemap
179
179
180 Some converter_sources (svn in particular) can claim that a file
180 Some converter_sources (svn in particular) can claim that a file
181 was changed in a revision, even if there was no change. This method
181 was changed in a revision, even if there was no change. This method
182 tells the destination that we're using a filemap and that it should
182 tells the destination that we're using a filemap and that it should
183 filter empty revisions.
183 filter empty revisions.
184 """
184 """
185 pass
185 pass
@@ -1,140 +1,139 b''
1 # git support for the convert extension
1 # git support for the convert extension
2
2
3 import os
3 import os
4 from mercurial import util
4 from mercurial import util
5
5
6 from common import NoRepo, commit, converter_source
6 from common import NoRepo, commit, converter_source
7
7
8 class convert_git(converter_source):
8 class convert_git(converter_source):
9 # Windows does not support GIT_DIR= construct while other systems
9 # Windows does not support GIT_DIR= construct while other systems
10 # cannot remove environment variable. Just assume none have
10 # cannot remove environment variable. Just assume none have
11 # both issues.
11 # both issues.
12 if hasattr(os, 'unsetenv'):
12 if hasattr(os, 'unsetenv'):
13 def gitcmd(self, s):
13 def gitcmd(self, s):
14 prevgitdir = os.environ.get('GIT_DIR')
14 prevgitdir = os.environ.get('GIT_DIR')
15 os.environ['GIT_DIR'] = self.path
15 os.environ['GIT_DIR'] = self.path
16 try:
16 try:
17 return util.popen(s)
17 return util.popen(s)
18 finally:
18 finally:
19 if prevgitdir is None:
19 if prevgitdir is None:
20 del os.environ['GIT_DIR']
20 del os.environ['GIT_DIR']
21 else:
21 else:
22 os.environ['GIT_DIR'] = prevgitdir
22 os.environ['GIT_DIR'] = prevgitdir
23 else:
23 else:
24 def gitcmd(self, s):
24 def gitcmd(self, s):
25 return util.popen('GIT_DIR=%s %s' % (self.path, s))
25 return util.popen('GIT_DIR=%s %s' % (self.path, s))
26
26
27 def __init__(self, ui, path, rev=None):
27 def __init__(self, ui, path, rev=None):
28 super(convert_git, self).__init__(ui, path, rev=rev)
28 super(convert_git, self).__init__(ui, path, rev=rev)
29
29
30 if os.path.isdir(path + "/.git"):
30 if os.path.isdir(path + "/.git"):
31 path += "/.git"
31 path += "/.git"
32 if not os.path.exists(path + "/objects"):
32 if not os.path.exists(path + "/objects"):
33 raise NoRepo("%s does not look like a Git repo" % path)
33 raise NoRepo("%s does not look like a Git repo" % path)
34 self.path = path
34 self.path = path
35
35
36 def getheads(self):
36 def getheads(self):
37 if not self.rev:
37 if not self.rev:
38 return self.gitcmd('git-rev-parse --branches').read().splitlines()
38 return self.gitcmd('git-rev-parse --branches').read().splitlines()
39 else:
39 else:
40 fh = self.gitcmd("git-rev-parse --verify %s" % self.rev)
40 fh = self.gitcmd("git-rev-parse --verify %s" % self.rev)
41 return [fh.read()[:-1]]
41 return [fh.read()[:-1]]
42
42
43 def catfile(self, rev, type):
43 def catfile(self, rev, type):
44 if rev == "0" * 40: raise IOError()
44 if rev == "0" * 40: raise IOError()
45 fh = self.gitcmd("git-cat-file %s %s" % (type, rev))
45 fh = self.gitcmd("git-cat-file %s %s" % (type, rev))
46 return fh.read()
46 return fh.read()
47
47
48 def getfile(self, name, rev):
48 def getfile(self, name, rev):
49 return self.catfile(rev, "blob")
49 return self.catfile(rev, "blob")
50
50
51 def getmode(self, name, rev):
51 def getmode(self, name, rev):
52 return self.modecache[(name, rev)]
52 return self.modecache[(name, rev)]
53
53
54 def getchanges(self, version):
54 def getchanges(self, version):
55 self.modecache = {}
55 self.modecache = {}
56 fh = self.gitcmd("git-diff-tree --root -m -r %s" % version)
56 fh = self.gitcmd("git-diff-tree --root -m -r %s" % version)
57 changes = []
57 changes = []
58 seen = {}
58 seen = {}
59 for l in fh:
59 for l in fh:
60 if "\t" not in l:
60 if "\t" not in l:
61 continue
61 continue
62 m, f = l[:-1].split("\t")
62 m, f = l[:-1].split("\t")
63 if f in seen:
63 if f in seen:
64 continue
64 continue
65 seen[f] = 1
65 seen[f] = 1
66 m = m.split()
66 m = m.split()
67 h = m[3]
67 h = m[3]
68 p = (m[1] == "100755")
68 p = (m[1] == "100755")
69 s = (m[1] == "120000")
69 s = (m[1] == "120000")
70 self.modecache[(f, h)] = (p and "x") or (s and "l") or ""
70 self.modecache[(f, h)] = (p and "x") or (s and "l") or ""
71 changes.append((f, h))
71 changes.append((f, h))
72 return (changes, {})
72 return (changes, {})
73
73
74 def getcommit(self, version):
74 def getcommit(self, version):
75 c = self.catfile(version, "commit") # read the commit hash
75 c = self.catfile(version, "commit") # read the commit hash
76 end = c.find("\n\n")
76 end = c.find("\n\n")
77 message = c[end+2:]
77 message = c[end+2:]
78 message = self.recode(message)
78 message = self.recode(message)
79 l = c[:end].splitlines()
79 l = c[:end].splitlines()
80 manifest = l[0].split()[1]
80 manifest = l[0].split()[1]
81 parents = []
81 parents = []
82 for e in l[1:]:
82 for e in l[1:]:
83 n, v = e.split(" ", 1)
83 n, v = e.split(" ", 1)
84 if n == "author":
84 if n == "author":
85 p = v.split()
85 p = v.split()
86 tm, tz = p[-2:]
86 tm, tz = p[-2:]
87 author = " ".join(p[:-2])
87 author = " ".join(p[:-2])
88 if author[0] == "<": author = author[1:-1]
88 if author[0] == "<": author = author[1:-1]
89 author = self.recode(author)
89 author = self.recode(author)
90 if n == "committer":
90 if n == "committer":
91 p = v.split()
91 p = v.split()
92 tm, tz = p[-2:]
92 tm, tz = p[-2:]
93 committer = " ".join(p[:-2])
93 committer = " ".join(p[:-2])
94 if committer[0] == "<": committer = committer[1:-1]
94 if committer[0] == "<": committer = committer[1:-1]
95 committer = self.recode(committer)
95 committer = self.recode(committer)
96 message += "\ncommitter: %s\n" % committer
96 message += "\ncommitter: %s\n" % committer
97 if n == "parent": parents.append(v)
97 if n == "parent": parents.append(v)
98
98
99 tzs, tzh, tzm = tz[-5:-4] + "1", tz[-4:-2], tz[-2:]
99 tzs, tzh, tzm = tz[-5:-4] + "1", tz[-4:-2], tz[-2:]
100 tz = -int(tzs) * (int(tzh) * 3600 + int(tzm))
100 tz = -int(tzs) * (int(tzh) * 3600 + int(tzm))
101 date = tm + " " + str(tz)
101 date = tm + " " + str(tz)
102 author = author or "unknown"
103
102
104 c = commit(parents=parents, date=date, author=author, desc=message,
103 c = commit(parents=parents, date=date, author=author, desc=message,
105 rev=version)
104 rev=version)
106 return c
105 return c
107
106
108 def gettags(self):
107 def gettags(self):
109 tags = {}
108 tags = {}
110 fh = self.gitcmd('git-ls-remote --tags "%s"' % self.path)
109 fh = self.gitcmd('git-ls-remote --tags "%s"' % self.path)
111 prefix = 'refs/tags/'
110 prefix = 'refs/tags/'
112 for line in fh:
111 for line in fh:
113 line = line.strip()
112 line = line.strip()
114 if not line.endswith("^{}"):
113 if not line.endswith("^{}"):
115 continue
114 continue
116 node, tag = line.split(None, 1)
115 node, tag = line.split(None, 1)
117 if not tag.startswith(prefix):
116 if not tag.startswith(prefix):
118 continue
117 continue
119 tag = tag[len(prefix):-3]
118 tag = tag[len(prefix):-3]
120 tags[tag] = node
119 tags[tag] = node
121
120
122 return tags
121 return tags
123
122
124 def getchangedfiles(self, version, i):
123 def getchangedfiles(self, version, i):
125 changes = []
124 changes = []
126 if i is None:
125 if i is None:
127 fh = self.gitcmd("git-diff-tree --root -m -r %s" % version)
126 fh = self.gitcmd("git-diff-tree --root -m -r %s" % version)
128 for l in fh:
127 for l in fh:
129 if "\t" not in l:
128 if "\t" not in l:
130 continue
129 continue
131 m, f = l[:-1].split("\t")
130 m, f = l[:-1].split("\t")
132 changes.append(f)
131 changes.append(f)
133 fh.close()
132 fh.close()
134 else:
133 else:
135 fh = self.gitcmd('git-diff-tree --name-only --root -r %s "%s^%s" --'
134 fh = self.gitcmd('git-diff-tree --name-only --root -r %s "%s^%s" --'
136 % (version, version, i+1))
135 % (version, version, i+1))
137 changes = [f.rstrip('\n') for f in fh]
136 changes = [f.rstrip('\n') for f in fh]
138 fh.close()
137 fh.close()
139
138
140 return changes
139 return changes
General Comments 0
You need to be logged in to leave comments. Login now