##// END OF EJS Templates
convert: hg.clonebranches must pull missing parents (issue941)
Patrick Mezard -
r5934:e495f3f3 default
parent child Browse files
Show More
@@ -0,0 +1,54 b''
1 #!/bin/sh
2
3 echo "[extensions]" >> $HGRCPATH
4 echo "hgext.convert = " >> $HGRCPATH
5 echo "[convert]" >> $HGRCPATH
6 echo "hg.tagsbranch=0" >> $HGRCPATH
7
8 hg init source
9 cd source
10 echo a > a
11 hg ci -qAm adda
12 # Add a merge with one parent in the same branch
13 echo a >> a
14 hg ci -qAm changea
15 hg up -qC 0
16 hg branch branch0
17 echo b > b
18 hg ci -qAm addb
19 hg up -qC
20 hg merge
21 hg ci -qm mergeab
22 hg tag -ql mergeab
23 cd ..
24
25 # Miss perl... sometimes
26 cat > filter.py <<EOF
27 import sys, re
28
29 r = re.compile(r'^(?:\d+|pulling from)')
30 sys.stdout.writelines([l for l in sys.stdin if r.search(l)])
31 EOF
32
33 echo % convert
34 hg convert -v --config convert.hg.clonebranches=1 source dest |
35 python filter.py
36
37 # Add a merge with both parents and child in different branches
38 cd source
39 hg branch branch1
40 echo a > file1
41 hg ci -qAm c1
42 hg up -qC mergeab
43 hg branch branch2
44 echo a > file2
45 hg ci -qAm c2
46 hg merge branch1
47 hg branch branch3
48 hg ci -qAm c3
49 cd ..
50
51 echo % incremental conversion
52 hg convert -v --config convert.hg.clonebranches=1 source dest |
53 python filter.py
54
@@ -0,0 +1,29 b''
1 marked working directory as branch branch0
2 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
3 (branch merge, don't forget to commit)
4 % convert
5 3 adda
6 2 addb
7 pulling from default into branch0
8 1 changesets found
9 1 changea
10 0 mergeab
11 pulling from default into branch0
12 1 changesets found
13 marked working directory as branch branch1
14 marked working directory as branch branch2
15 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
16 (branch merge, don't forget to commit)
17 marked working directory as branch branch3
18 % incremental conversion
19 2 c1
20 pulling from branch0 into branch1
21 2 changesets found
22 1 c2
23 pulling from branch0 into branch2
24 2 changesets found
25 0 c3
26 pulling from branch2 into branch3
27 3 changesets found
28 pulling from branch1 into branch3
29 1 changesets found
@@ -1,186 +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
26 self.date = date
26 self.date = date
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, pbranch, parents):
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 pbranch: branch name of parent commit
174 pbranches: (converted parent revision, parent branch) tuples"""
175 parents: destination revisions of parent"""
176 pass
175 pass
177
176
178 def setfilemapmode(self, active):
177 def setfilemapmode(self, active):
179 """Tell the destination that we're using a filemap
178 """Tell the destination that we're using a filemap
180
179
181 Some converter_sources (svn in particular) can claim that a file
180 Some converter_sources (svn in particular) can claim that a file
182 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
183 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
184 filter empty revisions.
183 filter empty revisions.
185 """
184 """
186 pass
185 pass
@@ -1,330 +1,330 b''
1 # convcmd - convert extension commands definition
1 # convcmd - convert extension commands definition
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from common import NoRepo, SKIPREV, converter_source, converter_sink
8 from common import NoRepo, SKIPREV, converter_source, converter_sink
9 from cvs import convert_cvs
9 from cvs import convert_cvs
10 from darcs import darcs_source
10 from darcs import darcs_source
11 from git import convert_git
11 from git import convert_git
12 from hg import mercurial_source, mercurial_sink
12 from hg import mercurial_source, mercurial_sink
13 from subversion import svn_source, debugsvnlog
13 from subversion import svn_source, debugsvnlog
14 import filemap
14 import filemap
15
15
16 import os, shutil
16 import os, shutil
17 from mercurial import hg, util
17 from mercurial import hg, util
18 from mercurial.i18n import _
18 from mercurial.i18n import _
19
19
20 source_converters = [
20 source_converters = [
21 ('cvs', convert_cvs),
21 ('cvs', convert_cvs),
22 ('git', convert_git),
22 ('git', convert_git),
23 ('svn', svn_source),
23 ('svn', svn_source),
24 ('hg', mercurial_source),
24 ('hg', mercurial_source),
25 ('darcs', darcs_source),
25 ('darcs', darcs_source),
26 ]
26 ]
27
27
28 sink_converters = [
28 sink_converters = [
29 ('hg', mercurial_sink),
29 ('hg', mercurial_sink),
30 ]
30 ]
31
31
32 def convertsource(ui, path, type, rev):
32 def convertsource(ui, path, type, rev):
33 exceptions = []
33 exceptions = []
34 for name, source in source_converters:
34 for name, source in source_converters:
35 try:
35 try:
36 if not type or name == type:
36 if not type or name == type:
37 return source(ui, path, rev)
37 return source(ui, path, rev)
38 except NoRepo, inst:
38 except NoRepo, inst:
39 exceptions.append(inst)
39 exceptions.append(inst)
40 if not ui.quiet:
40 if not ui.quiet:
41 for inst in exceptions:
41 for inst in exceptions:
42 ui.write(_("%s\n") % inst)
42 ui.write(_("%s\n") % inst)
43 raise util.Abort('%s: unknown repository type' % path)
43 raise util.Abort('%s: unknown repository type' % path)
44
44
45 def convertsink(ui, path, type):
45 def convertsink(ui, path, type):
46 for name, sink in sink_converters:
46 for name, sink in sink_converters:
47 try:
47 try:
48 if not type or name == type:
48 if not type or name == type:
49 return sink(ui, path)
49 return sink(ui, path)
50 except NoRepo, inst:
50 except NoRepo, inst:
51 ui.note(_("convert: %s\n") % inst)
51 ui.note(_("convert: %s\n") % inst)
52 raise util.Abort('%s: unknown repository type' % path)
52 raise util.Abort('%s: unknown repository type' % path)
53
53
54 class converter(object):
54 class converter(object):
55 def __init__(self, ui, source, dest, revmapfile, opts):
55 def __init__(self, ui, source, dest, revmapfile, opts):
56
56
57 self.source = source
57 self.source = source
58 self.dest = dest
58 self.dest = dest
59 self.ui = ui
59 self.ui = ui
60 self.opts = opts
60 self.opts = opts
61 self.commitcache = {}
61 self.commitcache = {}
62 self.revmapfile = revmapfile
62 self.revmapfile = revmapfile
63 self.revmapfilefd = None
63 self.revmapfilefd = None
64 self.authors = {}
64 self.authors = {}
65 self.authorfile = None
65 self.authorfile = None
66
66
67 self.maporder = []
67 self.maporder = []
68 self.map = {}
68 self.map = {}
69 try:
69 try:
70 origrevmapfile = open(self.revmapfile, 'r')
70 origrevmapfile = open(self.revmapfile, 'r')
71 for l in origrevmapfile:
71 for l in origrevmapfile:
72 sv, dv = l[:-1].split()
72 sv, dv = l[:-1].split()
73 if sv not in self.map:
73 if sv not in self.map:
74 self.maporder.append(sv)
74 self.maporder.append(sv)
75 self.map[sv] = dv
75 self.map[sv] = dv
76 origrevmapfile.close()
76 origrevmapfile.close()
77 except IOError:
77 except IOError:
78 pass
78 pass
79
79
80 # Read first the dst author map if any
80 # Read first the dst author map if any
81 authorfile = self.dest.authorfile()
81 authorfile = self.dest.authorfile()
82 if authorfile and os.path.exists(authorfile):
82 if authorfile and os.path.exists(authorfile):
83 self.readauthormap(authorfile)
83 self.readauthormap(authorfile)
84 # Extend/Override with new author map if necessary
84 # Extend/Override with new author map if necessary
85 if opts.get('authors'):
85 if opts.get('authors'):
86 self.readauthormap(opts.get('authors'))
86 self.readauthormap(opts.get('authors'))
87 self.authorfile = self.dest.authorfile()
87 self.authorfile = self.dest.authorfile()
88
88
89 def walktree(self, heads):
89 def walktree(self, heads):
90 '''Return a mapping that identifies the uncommitted parents of every
90 '''Return a mapping that identifies the uncommitted parents of every
91 uncommitted changeset.'''
91 uncommitted changeset.'''
92 visit = heads
92 visit = heads
93 known = {}
93 known = {}
94 parents = {}
94 parents = {}
95 while visit:
95 while visit:
96 n = visit.pop(0)
96 n = visit.pop(0)
97 if n in known or n in self.map: continue
97 if n in known or n in self.map: continue
98 known[n] = 1
98 known[n] = 1
99 commit = self.cachecommit(n)
99 commit = self.cachecommit(n)
100 parents[n] = []
100 parents[n] = []
101 for p in commit.parents:
101 for p in commit.parents:
102 parents[n].append(p)
102 parents[n].append(p)
103 visit.append(p)
103 visit.append(p)
104
104
105 return parents
105 return parents
106
106
107 def toposort(self, parents):
107 def toposort(self, parents):
108 '''Return an ordering such that every uncommitted changeset is
108 '''Return an ordering such that every uncommitted changeset is
109 preceeded by all its uncommitted ancestors.'''
109 preceeded by all its uncommitted ancestors.'''
110 visit = parents.keys()
110 visit = parents.keys()
111 seen = {}
111 seen = {}
112 children = {}
112 children = {}
113
113
114 while visit:
114 while visit:
115 n = visit.pop(0)
115 n = visit.pop(0)
116 if n in seen: continue
116 if n in seen: continue
117 seen[n] = 1
117 seen[n] = 1
118 # Ensure that nodes without parents are present in the 'children'
118 # Ensure that nodes without parents are present in the 'children'
119 # mapping.
119 # mapping.
120 children.setdefault(n, [])
120 children.setdefault(n, [])
121 for p in parents[n]:
121 for p in parents[n]:
122 if not p in self.map:
122 if not p in self.map:
123 visit.append(p)
123 visit.append(p)
124 children.setdefault(p, []).append(n)
124 children.setdefault(p, []).append(n)
125
125
126 s = []
126 s = []
127 removed = {}
127 removed = {}
128 visit = children.keys()
128 visit = children.keys()
129 while visit:
129 while visit:
130 n = visit.pop(0)
130 n = visit.pop(0)
131 if n in removed: continue
131 if n in removed: continue
132 dep = 0
132 dep = 0
133 if n in parents:
133 if n in parents:
134 for p in parents[n]:
134 for p in parents[n]:
135 if p in self.map: continue
135 if p in self.map: continue
136 if p not in removed:
136 if p not in removed:
137 # we're still dependent
137 # we're still dependent
138 visit.append(n)
138 visit.append(n)
139 dep = 1
139 dep = 1
140 break
140 break
141
141
142 if not dep:
142 if not dep:
143 # all n's parents are in the list
143 # all n's parents are in the list
144 removed[n] = 1
144 removed[n] = 1
145 if n not in self.map:
145 if n not in self.map:
146 s.append(n)
146 s.append(n)
147 if n in children:
147 if n in children:
148 for c in children[n]:
148 for c in children[n]:
149 visit.insert(0, c)
149 visit.insert(0, c)
150
150
151 if self.opts.get('datesort'):
151 if self.opts.get('datesort'):
152 depth = {}
152 depth = {}
153 for n in s:
153 for n in s:
154 depth[n] = 0
154 depth[n] = 0
155 pl = [p for p in self.commitcache[n].parents
155 pl = [p for p in self.commitcache[n].parents
156 if p not in self.map]
156 if p not in self.map]
157 if pl:
157 if pl:
158 depth[n] = max([depth[p] for p in pl]) + 1
158 depth[n] = max([depth[p] for p in pl]) + 1
159
159
160 s = [(depth[n], self.commitcache[n].date, n) for n in s]
160 s = [(depth[n], self.commitcache[n].date, n) for n in s]
161 s.sort()
161 s.sort()
162 s = [e[2] for e in s]
162 s = [e[2] for e in s]
163
163
164 return s
164 return s
165
165
166 def mapentry(self, src, dst):
166 def mapentry(self, src, dst):
167 if self.revmapfilefd is None:
167 if self.revmapfilefd is None:
168 try:
168 try:
169 self.revmapfilefd = open(self.revmapfile, "a")
169 self.revmapfilefd = open(self.revmapfile, "a")
170 except IOError, (errno, strerror):
170 except IOError, (errno, strerror):
171 raise util.Abort("Could not open map file %s: %s, %s\n" % (self.revmapfile, errno, strerror))
171 raise util.Abort("Could not open map file %s: %s, %s\n" % (self.revmapfile, errno, strerror))
172 self.map[src] = dst
172 self.map[src] = dst
173 self.revmapfilefd.write("%s %s\n" % (src, dst))
173 self.revmapfilefd.write("%s %s\n" % (src, dst))
174 self.revmapfilefd.flush()
174 self.revmapfilefd.flush()
175
175
176 def writeauthormap(self):
176 def writeauthormap(self):
177 authorfile = self.authorfile
177 authorfile = self.authorfile
178 if authorfile:
178 if authorfile:
179 self.ui.status('Writing author map file %s\n' % authorfile)
179 self.ui.status('Writing author map file %s\n' % authorfile)
180 ofile = open(authorfile, 'w+')
180 ofile = open(authorfile, 'w+')
181 for author in self.authors:
181 for author in self.authors:
182 ofile.write("%s=%s\n" % (author, self.authors[author]))
182 ofile.write("%s=%s\n" % (author, self.authors[author]))
183 ofile.close()
183 ofile.close()
184
184
185 def readauthormap(self, authorfile):
185 def readauthormap(self, authorfile):
186 afile = open(authorfile, 'r')
186 afile = open(authorfile, 'r')
187 for line in afile:
187 for line in afile:
188 try:
188 try:
189 srcauthor = line.split('=')[0].strip()
189 srcauthor = line.split('=')[0].strip()
190 dstauthor = line.split('=')[1].strip()
190 dstauthor = line.split('=')[1].strip()
191 if srcauthor in self.authors and dstauthor != self.authors[srcauthor]:
191 if srcauthor in self.authors and dstauthor != self.authors[srcauthor]:
192 self.ui.status(
192 self.ui.status(
193 'Overriding mapping for author %s, was %s, will be %s\n'
193 'Overriding mapping for author %s, was %s, will be %s\n'
194 % (srcauthor, self.authors[srcauthor], dstauthor))
194 % (srcauthor, self.authors[srcauthor], dstauthor))
195 else:
195 else:
196 self.ui.debug('Mapping author %s to %s\n'
196 self.ui.debug('Mapping author %s to %s\n'
197 % (srcauthor, dstauthor))
197 % (srcauthor, dstauthor))
198 self.authors[srcauthor] = dstauthor
198 self.authors[srcauthor] = dstauthor
199 except IndexError:
199 except IndexError:
200 self.ui.warn(
200 self.ui.warn(
201 'Ignoring bad line in author file map %s: %s\n'
201 'Ignoring bad line in author file map %s: %s\n'
202 % (authorfile, line))
202 % (authorfile, line))
203 afile.close()
203 afile.close()
204
204
205 def cachecommit(self, rev):
205 def cachecommit(self, rev):
206 commit = self.source.getcommit(rev)
206 commit = self.source.getcommit(rev)
207 commit.author = self.authors.get(commit.author, commit.author)
207 commit.author = self.authors.get(commit.author, commit.author)
208 self.commitcache[rev] = commit
208 self.commitcache[rev] = commit
209 return commit
209 return commit
210
210
211 def copy(self, rev):
211 def copy(self, rev):
212 commit = self.commitcache[rev]
212 commit = self.commitcache[rev]
213 do_copies = hasattr(self.dest, 'copyfile')
213 do_copies = hasattr(self.dest, 'copyfile')
214 filenames = []
214 filenames = []
215
215
216 changes = self.source.getchanges(rev)
216 changes = self.source.getchanges(rev)
217 if isinstance(changes, basestring):
217 if isinstance(changes, basestring):
218 if changes == SKIPREV:
218 if changes == SKIPREV:
219 dest = SKIPREV
219 dest = SKIPREV
220 else:
220 else:
221 dest = self.map[changes]
221 dest = self.map[changes]
222 self.mapentry(rev, dest)
222 self.mapentry(rev, dest)
223 return
223 return
224 files, copies = changes
224 files, copies = changes
225 parents = [self.map[r] for r in commit.parents]
225 pbranches = []
226 if commit.parents:
226 if commit.parents:
227 prev = commit.parents[0]
227 for prev in commit.parents:
228 if prev not in self.commitcache:
228 if prev not in self.commitcache:
229 self.cachecommit(prev)
229 self.cachecommit(prev)
230 pbranch = self.commitcache[prev].branch
230 pbranches.append((self.map[prev],
231 else:
231 self.commitcache[prev].branch))
232 pbranch = None
232 self.dest.setbranch(commit.branch, pbranches)
233 self.dest.setbranch(commit.branch, pbranch, parents)
234 for f, v in files:
233 for f, v in files:
235 filenames.append(f)
234 filenames.append(f)
236 try:
235 try:
237 data = self.source.getfile(f, v)
236 data = self.source.getfile(f, v)
238 except IOError, inst:
237 except IOError, inst:
239 self.dest.delfile(f)
238 self.dest.delfile(f)
240 else:
239 else:
241 e = self.source.getmode(f, v)
240 e = self.source.getmode(f, v)
242 self.dest.putfile(f, e, data)
241 self.dest.putfile(f, e, data)
243 if do_copies:
242 if do_copies:
244 if f in copies:
243 if f in copies:
245 copyf = copies[f]
244 copyf = copies[f]
246 # Merely marks that a copy happened.
245 # Merely marks that a copy happened.
247 self.dest.copyfile(copyf, f)
246 self.dest.copyfile(copyf, f)
248
247
248 parents = [b[0] for b in pbranches]
249 newnode = self.dest.putcommit(filenames, parents, commit)
249 newnode = self.dest.putcommit(filenames, parents, commit)
250 self.mapentry(rev, newnode)
250 self.mapentry(rev, newnode)
251
251
252 def convert(self):
252 def convert(self):
253 try:
253 try:
254 self.source.before()
254 self.source.before()
255 self.dest.before()
255 self.dest.before()
256 self.source.setrevmap(self.map, self.maporder)
256 self.source.setrevmap(self.map, self.maporder)
257 self.ui.status("scanning source...\n")
257 self.ui.status("scanning source...\n")
258 heads = self.source.getheads()
258 heads = self.source.getheads()
259 parents = self.walktree(heads)
259 parents = self.walktree(heads)
260 self.ui.status("sorting...\n")
260 self.ui.status("sorting...\n")
261 t = self.toposort(parents)
261 t = self.toposort(parents)
262 num = len(t)
262 num = len(t)
263 c = None
263 c = None
264
264
265 self.ui.status("converting...\n")
265 self.ui.status("converting...\n")
266 for c in t:
266 for c in t:
267 num -= 1
267 num -= 1
268 desc = self.commitcache[c].desc
268 desc = self.commitcache[c].desc
269 if "\n" in desc:
269 if "\n" in desc:
270 desc = desc.splitlines()[0]
270 desc = desc.splitlines()[0]
271 self.ui.status("%d %s\n" % (num, desc))
271 self.ui.status("%d %s\n" % (num, desc))
272 self.copy(c)
272 self.copy(c)
273
273
274 tags = self.source.gettags()
274 tags = self.source.gettags()
275 ctags = {}
275 ctags = {}
276 for k in tags:
276 for k in tags:
277 v = tags[k]
277 v = tags[k]
278 if self.map.get(v, SKIPREV) != SKIPREV:
278 if self.map.get(v, SKIPREV) != SKIPREV:
279 ctags[k] = self.map[v]
279 ctags[k] = self.map[v]
280
280
281 if c and ctags:
281 if c and ctags:
282 nrev = self.dest.puttags(ctags)
282 nrev = self.dest.puttags(ctags)
283 # write another hash correspondence to override the previous
283 # write another hash correspondence to override the previous
284 # one so we don't end up with extra tag heads
284 # one so we don't end up with extra tag heads
285 if nrev:
285 if nrev:
286 self.mapentry(c, nrev)
286 self.mapentry(c, nrev)
287
287
288 self.writeauthormap()
288 self.writeauthormap()
289 finally:
289 finally:
290 self.cleanup()
290 self.cleanup()
291
291
292 def cleanup(self):
292 def cleanup(self):
293 try:
293 try:
294 self.dest.after()
294 self.dest.after()
295 finally:
295 finally:
296 self.source.after()
296 self.source.after()
297 if self.revmapfilefd:
297 if self.revmapfilefd:
298 self.revmapfilefd.close()
298 self.revmapfilefd.close()
299
299
300 def convert(ui, src, dest=None, revmapfile=None, **opts):
300 def convert(ui, src, dest=None, revmapfile=None, **opts):
301 util._encoding = 'UTF-8'
301 util._encoding = 'UTF-8'
302
302
303 if not dest:
303 if not dest:
304 dest = hg.defaultdest(src) + "-hg"
304 dest = hg.defaultdest(src) + "-hg"
305 ui.status("assuming destination %s\n" % dest)
305 ui.status("assuming destination %s\n" % dest)
306
306
307 destc = convertsink(ui, dest, opts.get('dest_type'))
307 destc = convertsink(ui, dest, opts.get('dest_type'))
308
308
309 try:
309 try:
310 srcc = convertsource(ui, src, opts.get('source_type'),
310 srcc = convertsource(ui, src, opts.get('source_type'),
311 opts.get('rev'))
311 opts.get('rev'))
312 except Exception:
312 except Exception:
313 for path in destc.created:
313 for path in destc.created:
314 shutil.rmtree(path, True)
314 shutil.rmtree(path, True)
315 raise
315 raise
316
316
317 fmap = opts.get('filemap')
317 fmap = opts.get('filemap')
318 if fmap:
318 if fmap:
319 srcc = filemap.filemap_source(ui, srcc, fmap)
319 srcc = filemap.filemap_source(ui, srcc, fmap)
320 destc.setfilemapmode(True)
320 destc.setfilemapmode(True)
321
321
322 if not revmapfile:
322 if not revmapfile:
323 try:
323 try:
324 revmapfile = destc.revmapfile()
324 revmapfile = destc.revmapfile()
325 except:
325 except:
326 revmapfile = os.path.join(destc, "map")
326 revmapfile = os.path.join(destc, "map")
327
327
328 c = converter(ui, srcc, destc, revmapfile, opts)
328 c = converter(ui, srcc, destc, revmapfile, opts)
329 c.convert()
329 c.convert()
330
330
@@ -1,264 +1,277 b''
1 # hg backend for convert extension
1 # hg backend for convert extension
2
2
3 # Note for hg->hg conversion: Old versions of Mercurial didn't trim
3 # Note for hg->hg conversion: Old versions of Mercurial didn't trim
4 # the whitespace from the ends of commit messages, but new versions
4 # the whitespace from the ends of commit messages, but new versions
5 # do. Changesets created by those older versions, then converted, may
5 # do. Changesets created by those older versions, then converted, may
6 # thus have different hashes for changesets that are otherwise
6 # thus have different hashes for changesets that are otherwise
7 # identical.
7 # identical.
8
8
9
9
10 import os, time
10 import os, time
11 from mercurial.i18n import _
11 from mercurial.i18n import _
12 from mercurial.node import *
12 from mercurial.node import *
13 from mercurial import hg, lock, revlog, util
13 from mercurial import hg, lock, revlog, util
14
14
15 from common import NoRepo, commit, converter_source, converter_sink
15 from common import NoRepo, commit, converter_source, converter_sink
16
16
17 class mercurial_sink(converter_sink):
17 class mercurial_sink(converter_sink):
18 def __init__(self, ui, path):
18 def __init__(self, ui, path):
19 converter_sink.__init__(self, ui, path)
19 converter_sink.__init__(self, ui, path)
20 self.branchnames = ui.configbool('convert', 'hg.usebranchnames', True)
20 self.branchnames = ui.configbool('convert', 'hg.usebranchnames', True)
21 self.clonebranches = ui.configbool('convert', 'hg.clonebranches', False)
21 self.clonebranches = ui.configbool('convert', 'hg.clonebranches', False)
22 self.tagsbranch = ui.config('convert', 'hg.tagsbranch', 'default')
22 self.tagsbranch = ui.config('convert', 'hg.tagsbranch', 'default')
23 self.lastbranch = None
23 self.lastbranch = None
24 if os.path.isdir(path) and len(os.listdir(path)) > 0:
24 if os.path.isdir(path) and len(os.listdir(path)) > 0:
25 try:
25 try:
26 self.repo = hg.repository(self.ui, path)
26 self.repo = hg.repository(self.ui, path)
27 if not self.repo.local():
27 if not self.repo.local():
28 raise NoRepo(_('%s is not a local Mercurial repo') % path)
28 raise NoRepo(_('%s is not a local Mercurial repo') % path)
29 ui.status(_('destination %s is a Mercurial repository\n') %
29 ui.status(_('destination %s is a Mercurial repository\n') %
30 path)
30 path)
31 except hg.RepoError, err:
31 except hg.RepoError, err:
32 ui.print_exc()
32 ui.print_exc()
33 raise NoRepo(err.args[0])
33 raise NoRepo(err.args[0])
34 else:
34 else:
35 try:
35 try:
36 ui.status(_('initializing destination %s repository\n') % path)
36 ui.status(_('initializing destination %s repository\n') % path)
37 self.repo = hg.repository(self.ui, path, create=True)
37 self.repo = hg.repository(self.ui, path, create=True)
38 if not self.repo.local():
38 if not self.repo.local():
39 raise NoRepo(_('%s is not a local Mercurial repo') % path)
39 raise NoRepo(_('%s is not a local Mercurial repo') % path)
40 self.created.append(path)
40 self.created.append(path)
41 except hg.RepoError, err:
41 except hg.RepoError, err:
42 ui.print_exc()
42 ui.print_exc()
43 raise NoRepo("could not create hg repo %s as sink" % path)
43 raise NoRepo("could not create hg repo %s as sink" % path)
44 self.lock = None
44 self.lock = None
45 self.wlock = None
45 self.wlock = None
46 self.filemapmode = False
46 self.filemapmode = False
47
47
48 def before(self):
48 def before(self):
49 self.wlock = self.repo.wlock()
49 self.wlock = self.repo.wlock()
50 self.lock = self.repo.lock()
50 self.lock = self.repo.lock()
51 self.repo.dirstate.clear()
51 self.repo.dirstate.clear()
52
52
53 def after(self):
53 def after(self):
54 self.repo.dirstate.invalidate()
54 self.repo.dirstate.invalidate()
55 self.lock = None
55 self.lock = None
56 self.wlock = None
56 self.wlock = None
57
57
58 def revmapfile(self):
58 def revmapfile(self):
59 return os.path.join(self.path, ".hg", "shamap")
59 return os.path.join(self.path, ".hg", "shamap")
60
60
61 def authorfile(self):
61 def authorfile(self):
62 return os.path.join(self.path, ".hg", "authormap")
62 return os.path.join(self.path, ".hg", "authormap")
63
63
64 def getheads(self):
64 def getheads(self):
65 h = self.repo.changelog.heads()
65 h = self.repo.changelog.heads()
66 return [ hex(x) for x in h ]
66 return [ hex(x) for x in h ]
67
67
68 def putfile(self, f, e, data):
68 def putfile(self, f, e, data):
69 self.repo.wwrite(f, data, e)
69 self.repo.wwrite(f, data, e)
70 if f not in self.repo.dirstate:
70 if f not in self.repo.dirstate:
71 self.repo.dirstate.normallookup(f)
71 self.repo.dirstate.normallookup(f)
72
72
73 def copyfile(self, source, dest):
73 def copyfile(self, source, dest):
74 self.repo.copy(source, dest)
74 self.repo.copy(source, dest)
75
75
76 def delfile(self, f):
76 def delfile(self, f):
77 try:
77 try:
78 util.unlink(self.repo.wjoin(f))
78 util.unlink(self.repo.wjoin(f))
79 #self.repo.remove([f])
79 #self.repo.remove([f])
80 except OSError:
80 except OSError:
81 pass
81 pass
82
82
83 def setbranch(self, branch, pbranch, parents):
83 def setbranch(self, branch, pbranches):
84 if (not self.clonebranches) or (branch == self.lastbranch):
84 if not self.clonebranches:
85 return
85 return
86
86
87 setbranch = (branch != self.lastbranch)
87 self.lastbranch = branch
88 self.lastbranch = branch
88 self.after()
89 if not branch:
89 if not branch:
90 branch = 'default'
90 branch = 'default'
91 if not pbranch:
91 pbranches = [(b[0], b[1] and b[1] or 'default') for b in pbranches]
92 pbranch = 'default'
92 pbranch = pbranches and pbranches[0][1] or 'default'
93
93
94 branchpath = os.path.join(self.path, branch)
94 branchpath = os.path.join(self.path, branch)
95 try:
95 if setbranch:
96 self.repo = hg.repository(self.ui, branchpath)
96 self.after()
97 except:
97 try:
98 if not parents:
98 self.repo = hg.repository(self.ui, branchpath)
99 except:
99 self.repo = hg.repository(self.ui, branchpath, create=True)
100 self.repo = hg.repository(self.ui, branchpath, create=True)
100 else:
101 self.before()
101 self.ui.note(_('cloning branch %s to %s\n') % (pbranch, branch))
102
102 hg.clone(self.ui, os.path.join(self.path, pbranch),
103 # pbranches may bring revisions from other branches (merge parents)
103 branchpath, rev=parents, update=False,
104 # Make sure we have them, or pull them.
104 stream=True)
105 missings = {}
105 self.repo = hg.repository(self.ui, branchpath)
106 for b in pbranches:
106 self.before()
107 try:
108 self.repo.lookup(b[0])
109 except:
110 missings.setdefault(b[1], []).append(b[0])
111
112 if missings:
113 self.after()
114 for pbranch, heads in missings.iteritems():
115 pbranchpath = os.path.join(self.path, pbranch)
116 prepo = hg.repository(self.ui, pbranchpath)
117 self.ui.note(_('pulling from %s into %s\n') % (pbranch, branch))
118 self.repo.pull(prepo, [prepo.lookup(h) for h in heads])
119 self.before()
107
120
108 def putcommit(self, files, parents, commit):
121 def putcommit(self, files, parents, commit):
109 seen = {}
122 seen = {}
110 pl = []
123 pl = []
111 for p in parents:
124 for p in parents:
112 if p not in seen:
125 if p not in seen:
113 pl.append(p)
126 pl.append(p)
114 seen[p] = 1
127 seen[p] = 1
115 parents = pl
128 parents = pl
116 nparents = len(parents)
129 nparents = len(parents)
117 if self.filemapmode and nparents == 1:
130 if self.filemapmode and nparents == 1:
118 m1node = self.repo.changelog.read(bin(parents[0]))[0]
131 m1node = self.repo.changelog.read(bin(parents[0]))[0]
119 parent = parents[0]
132 parent = parents[0]
120
133
121 if len(parents) < 2: parents.append("0" * 40)
134 if len(parents) < 2: parents.append("0" * 40)
122 if len(parents) < 2: parents.append("0" * 40)
135 if len(parents) < 2: parents.append("0" * 40)
123 p2 = parents.pop(0)
136 p2 = parents.pop(0)
124
137
125 text = commit.desc
138 text = commit.desc
126 extra = commit.extra.copy()
139 extra = commit.extra.copy()
127 if self.branchnames and commit.branch:
140 if self.branchnames and commit.branch:
128 extra['branch'] = commit.branch
141 extra['branch'] = commit.branch
129 if commit.rev:
142 if commit.rev:
130 extra['convert_revision'] = commit.rev
143 extra['convert_revision'] = commit.rev
131
144
132 while parents:
145 while parents:
133 p1 = p2
146 p1 = p2
134 p2 = parents.pop(0)
147 p2 = parents.pop(0)
135 a = self.repo.rawcommit(files, text, commit.author, commit.date,
148 a = self.repo.rawcommit(files, text, commit.author, commit.date,
136 bin(p1), bin(p2), extra=extra)
149 bin(p1), bin(p2), extra=extra)
137 self.repo.dirstate.clear()
150 self.repo.dirstate.clear()
138 text = "(octopus merge fixup)\n"
151 text = "(octopus merge fixup)\n"
139 p2 = hg.hex(self.repo.changelog.tip())
152 p2 = hg.hex(self.repo.changelog.tip())
140
153
141 if self.filemapmode and nparents == 1:
154 if self.filemapmode and nparents == 1:
142 man = self.repo.manifest
155 man = self.repo.manifest
143 mnode = self.repo.changelog.read(bin(p2))[0]
156 mnode = self.repo.changelog.read(bin(p2))[0]
144 if not man.cmp(m1node, man.revision(mnode)):
157 if not man.cmp(m1node, man.revision(mnode)):
145 self.repo.rollback()
158 self.repo.rollback()
146 self.repo.dirstate.clear()
159 self.repo.dirstate.clear()
147 return parent
160 return parent
148 return p2
161 return p2
149
162
150 def puttags(self, tags):
163 def puttags(self, tags):
151 try:
164 try:
152 old = self.repo.wfile(".hgtags").read()
165 old = self.repo.wfile(".hgtags").read()
153 oldlines = old.splitlines(1)
166 oldlines = old.splitlines(1)
154 oldlines.sort()
167 oldlines.sort()
155 except:
168 except:
156 oldlines = []
169 oldlines = []
157
170
158 k = tags.keys()
171 k = tags.keys()
159 k.sort()
172 k.sort()
160 newlines = []
173 newlines = []
161 for tag in k:
174 for tag in k:
162 newlines.append("%s %s\n" % (tags[tag], tag))
175 newlines.append("%s %s\n" % (tags[tag], tag))
163
176
164 newlines.sort()
177 newlines.sort()
165
178
166 if newlines != oldlines:
179 if newlines != oldlines:
167 self.ui.status("updating tags\n")
180 self.ui.status("updating tags\n")
168 f = self.repo.wfile(".hgtags", "w")
181 f = self.repo.wfile(".hgtags", "w")
169 f.write("".join(newlines))
182 f.write("".join(newlines))
170 f.close()
183 f.close()
171 if not oldlines: self.repo.add([".hgtags"])
184 if not oldlines: self.repo.add([".hgtags"])
172 date = "%s 0" % int(time.mktime(time.gmtime()))
185 date = "%s 0" % int(time.mktime(time.gmtime()))
173 extra = {}
186 extra = {}
174 if self.tagsbranch != 'default':
187 if self.tagsbranch != 'default':
175 extra['branch'] = self.tagsbranch
188 extra['branch'] = self.tagsbranch
176 try:
189 try:
177 tagparent = self.repo.changectx(self.tagsbranch).node()
190 tagparent = self.repo.changectx(self.tagsbranch).node()
178 except hg.RepoError, inst:
191 except hg.RepoError, inst:
179 tagparent = nullid
192 tagparent = nullid
180 self.repo.rawcommit([".hgtags"], "update tags", "convert-repo",
193 self.repo.rawcommit([".hgtags"], "update tags", "convert-repo",
181 date, tagparent, nullid)
194 date, tagparent, nullid)
182 return hex(self.repo.changelog.tip())
195 return hex(self.repo.changelog.tip())
183
196
184 def setfilemapmode(self, active):
197 def setfilemapmode(self, active):
185 self.filemapmode = active
198 self.filemapmode = active
186
199
187 class mercurial_source(converter_source):
200 class mercurial_source(converter_source):
188 def __init__(self, ui, path, rev=None):
201 def __init__(self, ui, path, rev=None):
189 converter_source.__init__(self, ui, path, rev)
202 converter_source.__init__(self, ui, path, rev)
190 try:
203 try:
191 self.repo = hg.repository(self.ui, path)
204 self.repo = hg.repository(self.ui, path)
192 # try to provoke an exception if this isn't really a hg
205 # try to provoke an exception if this isn't really a hg
193 # repo, but some other bogus compatible-looking url
206 # repo, but some other bogus compatible-looking url
194 if not self.repo.local():
207 if not self.repo.local():
195 raise hg.RepoError()
208 raise hg.RepoError()
196 except hg.RepoError:
209 except hg.RepoError:
197 ui.print_exc()
210 ui.print_exc()
198 raise NoRepo("%s is not a local Mercurial repo" % path)
211 raise NoRepo("%s is not a local Mercurial repo" % path)
199 self.lastrev = None
212 self.lastrev = None
200 self.lastctx = None
213 self.lastctx = None
201 self._changescache = None
214 self._changescache = None
202
215
203 def changectx(self, rev):
216 def changectx(self, rev):
204 if self.lastrev != rev:
217 if self.lastrev != rev:
205 self.lastctx = self.repo.changectx(rev)
218 self.lastctx = self.repo.changectx(rev)
206 self.lastrev = rev
219 self.lastrev = rev
207 return self.lastctx
220 return self.lastctx
208
221
209 def getheads(self):
222 def getheads(self):
210 if self.rev:
223 if self.rev:
211 return [hex(self.repo.changectx(self.rev).node())]
224 return [hex(self.repo.changectx(self.rev).node())]
212 else:
225 else:
213 return [hex(node) for node in self.repo.heads()]
226 return [hex(node) for node in self.repo.heads()]
214
227
215 def getfile(self, name, rev):
228 def getfile(self, name, rev):
216 try:
229 try:
217 return self.changectx(rev).filectx(name).data()
230 return self.changectx(rev).filectx(name).data()
218 except revlog.LookupError, err:
231 except revlog.LookupError, err:
219 raise IOError(err)
232 raise IOError(err)
220
233
221 def getmode(self, name, rev):
234 def getmode(self, name, rev):
222 m = self.changectx(rev).manifest()
235 m = self.changectx(rev).manifest()
223 return (m.execf(name) and 'x' or '') + (m.linkf(name) and 'l' or '')
236 return (m.execf(name) and 'x' or '') + (m.linkf(name) and 'l' or '')
224
237
225 def getchanges(self, rev):
238 def getchanges(self, rev):
226 ctx = self.changectx(rev)
239 ctx = self.changectx(rev)
227 if self._changescache and self._changescache[0] == rev:
240 if self._changescache and self._changescache[0] == rev:
228 m, a, r = self._changescache[1]
241 m, a, r = self._changescache[1]
229 else:
242 else:
230 m, a, r = self.repo.status(ctx.parents()[0].node(), ctx.node())[:3]
243 m, a, r = self.repo.status(ctx.parents()[0].node(), ctx.node())[:3]
231 changes = [(name, rev) for name in m + a + r]
244 changes = [(name, rev) for name in m + a + r]
232 changes.sort()
245 changes.sort()
233 return (changes, self.getcopies(ctx, m + a))
246 return (changes, self.getcopies(ctx, m + a))
234
247
235 def getcopies(self, ctx, files):
248 def getcopies(self, ctx, files):
236 copies = {}
249 copies = {}
237 for name in files:
250 for name in files:
238 try:
251 try:
239 copies[name] = ctx.filectx(name).renamed()[0]
252 copies[name] = ctx.filectx(name).renamed()[0]
240 except TypeError:
253 except TypeError:
241 pass
254 pass
242 return copies
255 return copies
243
256
244 def getcommit(self, rev):
257 def getcommit(self, rev):
245 ctx = self.changectx(rev)
258 ctx = self.changectx(rev)
246 parents = [hex(p.node()) for p in ctx.parents() if p.node() != nullid]
259 parents = [hex(p.node()) for p in ctx.parents() if p.node() != nullid]
247 return commit(author=ctx.user(), date=util.datestr(ctx.date()),
260 return commit(author=ctx.user(), date=util.datestr(ctx.date()),
248 desc=ctx.description(), parents=parents,
261 desc=ctx.description(), parents=parents,
249 branch=ctx.branch(), extra=ctx.extra())
262 branch=ctx.branch(), extra=ctx.extra())
250
263
251 def gettags(self):
264 def gettags(self):
252 tags = [t for t in self.repo.tagslist() if t[0] != 'tip']
265 tags = [t for t in self.repo.tagslist() if t[0] != 'tip']
253 return dict([(name, hex(node)) for name, node in tags])
266 return dict([(name, hex(node)) for name, node in tags])
254
267
255 def getchangedfiles(self, rev, i):
268 def getchangedfiles(self, rev, i):
256 ctx = self.changectx(rev)
269 ctx = self.changectx(rev)
257 i = i or 0
270 i = i or 0
258 changes = self.repo.status(ctx.parents()[i].node(), ctx.node())[:3]
271 changes = self.repo.status(ctx.parents()[i].node(), ctx.node())[:3]
259
272
260 if i == 0:
273 if i == 0:
261 self._changescache = (rev, changes)
274 self._changescache = (rev, changes)
262
275
263 return changes[0] + changes[1] + changes[2]
276 return changes[0] + changes[1] + changes[2]
264
277
General Comments 0
You need to be logged in to leave comments. Login now