##// END OF EJS Templates
convert/mtn: handle new files in moved directories (issue1619)...
Patrick Mezard -
r8099:3cdf4872 default
parent child Browse files
Show More
@@ -1,200 +1,207 b''
1 1 # monotone support for the convert extension
2 2
3 3 import os, re, time
4 4 from mercurial import util
5 5 from common import NoRepo, MissingTool, commit, converter_source, checktool
6 6 from common import commandline
7 7 from mercurial.i18n import _
8 8
9 9 class monotone_source(converter_source, commandline):
10 10 def __init__(self, ui, path=None, rev=None):
11 11 converter_source.__init__(self, ui, path, rev)
12 12 commandline.__init__(self, ui, 'mtn')
13 13
14 14 self.ui = ui
15 15 self.path = path
16 16
17 17 # regular expressions for parsing monotone output
18 18 space = r'\s*'
19 19 name = r'\s+"((?:\\"|[^"])*)"\s*'
20 20 value = name
21 21 revision = r'\s+\[(\w+)\]\s*'
22 22 lines = r'(?:.|\n)+'
23 23
24 24 self.dir_re = re.compile(space + "dir" + name)
25 25 self.file_re = re.compile(space + "file" + name + "content" + revision)
26 26 self.add_file_re = re.compile(space + "add_file" + name + "content" + revision)
27 27 self.patch_re = re.compile(space + "patch" + name + "from" + revision + "to" + revision)
28 28 self.rename_re = re.compile(space + "rename" + name + "to" + name)
29 29 self.delete_re = re.compile(space + "delete" + name)
30 30 self.tag_re = re.compile(space + "tag" + name + "revision" + revision)
31 31 self.cert_re = re.compile(lines + space + "name" + name + "value" + value)
32 32
33 33 attr = space + "file" + lines + space + "attr" + space
34 34 self.attr_execute_re = re.compile(attr + '"mtn:execute"' + space + '"true"')
35 35
36 36 # cached data
37 37 self.manifest_rev = None
38 38 self.manifest = None
39 39 self.files = None
40 40 self.dirs = None
41 41
42 42 norepo = NoRepo (_("%s does not look like a monotone repo") % path)
43 43 if not os.path.exists(path):
44 44 raise norepo
45 45
46 46 checktool('mtn', abort=False)
47 47
48 48 # test if there are any revisions
49 49 self.rev = None
50 50 try:
51 51 self.getheads()
52 52 except:
53 53 raise norepo
54 54 self.rev = rev
55 55
56 56 def mtnrun(self, *args, **kwargs):
57 57 kwargs['d'] = self.path
58 58 return self.run0('automate', *args, **kwargs)
59 59
60 60 def mtnloadmanifest(self, rev):
61 61 if self.manifest_rev == rev:
62 62 return
63 63 self.manifest = self.mtnrun("get_manifest_of", rev).split("\n\n")
64 64 self.manifest_rev = rev
65 65 self.files = {}
66 66 self.dirs = {}
67 67
68 68 for e in self.manifest:
69 69 m = self.file_re.match(e)
70 70 if m:
71 71 attr = ""
72 72 name = m.group(1)
73 73 node = m.group(2)
74 74 if self.attr_execute_re.match(e):
75 75 attr += "x"
76 76 self.files[name] = (node, attr)
77 77 m = self.dir_re.match(e)
78 78 if m:
79 79 self.dirs[m.group(1)] = True
80 80
81 81 def mtnisfile(self, name, rev):
82 82 # a non-file could be a directory or a deleted or renamed file
83 83 self.mtnloadmanifest(rev)
84 84 try:
85 85 self.files[name]
86 86 return True
87 87 except KeyError:
88 88 return False
89 89
90 90 def mtnisdir(self, name, rev):
91 91 self.mtnloadmanifest(rev)
92 92 try:
93 93 self.dirs[name]
94 94 return True
95 95 except KeyError:
96 96 return False
97 97
98 98 def mtngetcerts(self, rev):
99 99 certs = {"author":"<missing>", "date":"<missing>",
100 100 "changelog":"<missing>", "branch":"<missing>"}
101 101 cert_list = self.mtnrun("certs", rev).split('\n\n key "')
102 102 for e in cert_list:
103 103 m = self.cert_re.match(e)
104 104 if m:
105 105 name, value = m.groups()
106 106 value = value.replace(r'\"', '"')
107 107 value = value.replace(r'\\', '\\')
108 108 certs[name] = value
109 109 return certs
110 110
111 def mtnrenamefiles(self, files, fromdir, todir):
112 renamed = {}
113 for tofile in files:
114 if tofile.startswith(todir + '/'):
115 renamed[tofile] = fromdir + tofile[len(todir):]
116 return renamed
117
118 111 # implement the converter_source interface:
119 112
120 113 def getheads(self):
121 114 if not self.rev:
122 115 return self.mtnrun("leaves").splitlines()
123 116 else:
124 117 return [self.rev]
125 118
126 119 def getchanges(self, rev):
127 120 #revision = self.mtncmd("get_revision %s" % rev).split("\n\n")
128 121 revision = self.mtnrun("get_revision", rev).split("\n\n")
129 122 files = {}
123 addedfiles = {}
124 renameddirs = []
130 125 copies = {}
131 126 for e in revision:
132 127 m = self.add_file_re.match(e)
133 128 if m:
134 129 files[m.group(1)] = rev
130 addedfiles[m.group(1)] = rev
135 131 m = self.patch_re.match(e)
136 132 if m:
137 133 files[m.group(1)] = rev
138
139 134 # Delete/rename is handled later when the convert engine
140 135 # discovers an IOError exception from getfile,
141 136 # but only if we add the "from" file to the list of changes.
142 137 m = self.delete_re.match(e)
143 138 if m:
144 139 files[m.group(1)] = rev
145 140 m = self.rename_re.match(e)
146 141 if m:
147 142 toname = m.group(2)
148 143 fromname = m.group(1)
149 144 if self.mtnisfile(toname, rev):
150 145 copies[toname] = fromname
151 146 files[toname] = rev
152 147 files[fromname] = rev
153 if self.mtnisdir(toname, rev):
154 renamed = self.mtnrenamefiles(self.files, fromname, toname)
155 for tofile, fromfile in renamed.items():
156 self.ui.debug (_("copying file in renamed dir from '%s' to '%s'") % (fromfile, tofile), '\n')
157 files[tofile] = rev
158 copies[tofile] = fromfile
159 for fromfile in renamed.values():
160 files[fromfile] = rev
148 elif self.mtnisdir(toname, rev):
149 renameddirs.append((fromname, toname))
150
151 # Directory renames can be handled only once we have recorded
152 # all new files
153 for fromdir, todir in renameddirs:
154 renamed = {}
155 for tofile in self.files:
156 if tofile in addedfiles:
157 continue
158 if tofile.startswith(todir + '/'):
159 renamed[tofile] = fromdir + tofile[len(todir):]
160 for tofile, fromfile in renamed.items():
161 self.ui.debug (_("copying file in renamed dir from '%s' to '%s'")
162 % (fromfile, tofile), '\n')
163 files[tofile] = rev
164 copies[tofile] = fromfile
165 for fromfile in renamed.values():
166 files[fromfile] = rev
167
161 168 return (files.items(), copies)
162 169
163 170 def getmode(self, name, rev):
164 171 self.mtnloadmanifest(rev)
165 172 try:
166 173 node, attr = self.files[name]
167 174 return attr
168 175 except KeyError:
169 176 return ""
170 177
171 178 def getfile(self, name, rev):
172 179 if not self.mtnisfile(name, rev):
173 180 raise IOError() # file was deleted or renamed
174 181 try:
175 182 return self.mtnrun("get_file_of", name, r=rev)
176 183 except:
177 184 raise IOError() # file was deleted or renamed
178 185
179 186 def getcommit(self, rev):
180 187 certs = self.mtngetcerts(rev)
181 188 return commit(
182 189 author=certs["author"],
183 190 date=util.datestr(util.strdate(certs["date"], "%Y-%m-%dT%H:%M:%S")),
184 191 desc=certs["changelog"],
185 192 rev=rev,
186 193 parents=self.mtnrun("parents", rev).splitlines(),
187 194 branch=certs["branch"])
188 195
189 196 def gettags(self):
190 197 tags = {}
191 198 for e in self.mtnrun("tags").split("\n\n"):
192 199 m = self.tag_re.match(e)
193 200 if m:
194 201 tags[m.group(1)] = m.group(2)
195 202 return tags
196 203
197 204 def getchangedfiles(self, rev, i):
198 205 # This function is only needed to support --filemap
199 206 # ... and we don't support that
200 207 raise NotImplementedError()
@@ -1,106 +1,112 b''
1 1 #!/bin/sh
2 2
3 3 "$TESTDIR/hghave" mtn || exit 80
4 4
5 5 # Monotone directory is called .monotone on *nix and monotone
6 6 # on Windows. Having a variable here ease test patching.
7 7 mtndir=.monotone
8 8 echo "[extensions]" >> $HGRCPATH
9 9 echo "convert=" >> $HGRCPATH
10 10 echo 'hgext.graphlog =' >> $HGRCPATH
11 11
12 12 HOME=`pwd`/do_not_use_HOME_mtn; export HOME
13 13 # Windows version of monotone home
14 14 APPDATA=$HOME; export APPDATA
15 15
16 16 echo % tedious monotone keys configuration
17 17 # The /dev/null redirection is necessary under Windows, or
18 18 # it complains about home directory permissions
19 19 mtn --quiet genkey test@selenic.com 1>/dev/null 2>&1 <<EOF
20 20 passphrase
21 21 passphrase
22 22 EOF
23 23 cat >> $HOME/$mtndir/monotonerc <<EOF
24 24 function get_passphrase(keypair_id)
25 25 return "passphrase"
26 26 end
27 27 EOF
28 28
29 29 echo % create monotone repository
30 30 mtn db init --db=repo.mtn
31 31 mtn --db=repo.mtn --branch=com.selenic.test setup workingdir
32 32 cd workingdir
33 33 echo a > a
34 34 mkdir dir
35 35 echo b > dir/b
36 echo d > dir/d
36 37 python -c 'file("bin", "wb").write("a\\x00b")'
37 38 echo c > c
38 mtn add a dir/b c bin
39 mtn add a dir/b dir/d c bin
39 40 mtn ci -m initialize
40 41 echo % update monotone working directory
41 42 mtn mv a dir/a
42 43 echo a >> dir/a
43 44 echo b >> dir/b
44 45 mtn drop c
45 46 python -c 'file("bin", "wb").write("b\\x00c")'
46 47 mtn ci -m update1
47 48 cd ..
48 49
49 50 echo % convert once
50 51 hg convert -s mtn repo.mtn
51 52
52 53 cd workingdir
53 54 echo e > e
54 55 mtn add e
55 56 mtn drop dir/b
56 57 mtn mv bin bin2
57 58 mtn ci -m 'update2 "with" quotes'
58 59 echo '% test directory move'
59 60 mkdir -p dir1/subdir1
60 61 mkdir -p dir1/subdir2_other
61 62 echo file1 > dir1/subdir1/file1
62 63 echo file2 > dir1/subdir2_other/file1
63 64 mtn add dir1/subdir1/file1 dir1/subdir2_other/file1
64 65 mtn ci -m createdir1
65 66 mtn rename dir1/subdir1 dir1/subdir2
66 67 mtn ci -m movedir1
67 68 echo '% test subdirectory move'
68 69 mtn mv dir dir2
70 echo newfile > dir2/newfile
71 mtn drop dir2/d
72 mtn add dir2/newfile
69 73 mtn ci -m movedir
70 74 # Test directory removal with empty directory
71 75 mkdir dir2/dir
72 76 mkdir dir2/dir/subdir
73 77 echo f > dir2/dir/subdir/f
74 78 mkdir dir2/dir/emptydir
75 79 mtn add --quiet -R dir2/dir
76 80 mtn ci -m emptydir
77 81 mtn drop -R dir2/dir
78 82 mtn ci -m dropdirectory
79 83 cd ..
80 84
81 85 echo % convert incrementally
82 86 hg convert -s mtn repo.mtn
83 87
84 88 glog()
85 89 {
86 90 hg glog --template '#rev# "#desc|firstline#" files: #files#\n' "$@"
87 91 }
88 92
89 93 cd repo.mtn-hg
90 94 hg up -C
91 95 glog
92 96 echo % manifest
93 97 hg manifest
94 98 echo % contents
95 99 cat dir2/a
96 100 test -d dir2/dir && echo 'removed dir2/dir is still there!'
97 101
98 102 echo % file move
99 103 hg log -v -C -r 1 | grep copies
100 104 echo % check directory move
101 105 hg manifest -r 4
102 106 test -d dir1/subdir2 || echo 'new dir1/subdir2 does not exist!'
103 107 test -d dir1/subdir1 && echo 'renamed dir1/subdir1 is still there!'
104 108 hg log -v -C -r 4 | grep copies
109 echo % check file remove with directory move
110 hg manifest -r 5
105 111 exit 0
106 112
@@ -1,98 +1,110 b''
1 1 % tedious monotone keys configuration
2 2 % create monotone repository
3 3 mtn: adding a to workspace manifest
4 4 mtn: adding bin to workspace manifest
5 5 mtn: adding c to workspace manifest
6 6 mtn: adding dir to workspace manifest
7 7 mtn: adding dir/b to workspace manifest
8 mtn: adding dir/d to workspace manifest
8 9 mtn: beginning commit on branch 'com.selenic.test'
9 mtn: committed revision 803ef0bf815e35b951dbd4310acd1e45e675016e
10 mtn: committed revision 0f6e5e4f2e7d2a8ef312408f57618abf026afd90
10 11 % update monotone working directory
11 12 mtn: skipping dir, already accounted for in workspace
12 13 mtn: renaming a to dir/a in workspace manifest
13 14 mtn: dropping c from workspace manifest
14 15 mtn: beginning commit on branch 'com.selenic.test'
15 mtn: committed revision 4daf60753d6fe21a06ce5f716303fe55fd6d3a56
16 mtn: committed revision 51d0a982464573a2a2cf5ee2c9219c652aaebeff
16 17 % convert once
17 18 assuming destination repo.mtn-hg
18 19 initializing destination repo.mtn-hg repository
19 20 scanning source...
20 21 sorting...
21 22 converting...
22 23 1 initialize
23 24 0 update1
24 25 mtn: adding e to workspace manifest
25 26 mtn: dropping dir/b from workspace manifest
26 27 mtn: renaming bin to bin2 in workspace manifest
27 28 mtn: beginning commit on branch 'com.selenic.test'
28 mtn: committed revision 6c6977a6ef609ec80e40779f89dbd2772c96de62
29 mtn: committed revision ebe58335d85d8cb176b6d0a12be04f5314b998da
29 30 % test directory move
30 31 mtn: adding dir1 to workspace manifest
31 32 mtn: adding dir1/subdir1 to workspace manifest
32 33 mtn: adding dir1/subdir1/file1 to workspace manifest
33 34 mtn: adding dir1/subdir2_other to workspace manifest
34 35 mtn: adding dir1/subdir2_other/file1 to workspace manifest
35 36 mtn: beginning commit on branch 'com.selenic.test'
36 mtn: committed revision e066b1feb2b7a7110450c2c18b5b4462011427d1
37 mtn: committed revision a8d62bc04fee4d2936d28e98bbcc81686dd74306
37 38 mtn: skipping dir1, already accounted for in workspace
38 39 mtn: renaming dir1/subdir1 to dir1/subdir2 in workspace manifest
39 40 mtn: beginning commit on branch 'com.selenic.test'
40 mtn: committed revision 2ad2409d25bb8d2583b57a3d4c0fa1df62aa1f79
41 mtn: committed revision 2c3d241bbbfe538b1b51d910f5676407e3f4d3a6
41 42 % test subdirectory move
42 43 mtn: renaming dir to dir2 in workspace manifest
44 mtn: dropping dir2/d from workspace manifest
45 mtn: adding dir2/newfile to workspace manifest
43 46 mtn: beginning commit on branch 'com.selenic.test'
44 mtn: committed revision a85290b81fc4a8fbce4dc4d956404109842b406e
47 mtn: committed revision fdb5a02dae8bfce3a79b3393680af471016e1b4c
45 48 mtn: beginning commit on branch 'com.selenic.test'
46 mtn: committed revision 7e3c8746060117104f16ff2d9212cf0f810cbff0
49 mtn: committed revision 8bbf76d717001d24964e4604739fdcd0f539fc88
47 50 mtn: dropping dir2/dir/subdir/f from workspace manifest
48 51 mtn: dropping dir2/dir/subdir from workspace manifest
49 52 mtn: dropping dir2/dir/emptydir from workspace manifest
50 53 mtn: dropping dir2/dir from workspace manifest
51 54 mtn: beginning commit on branch 'com.selenic.test'
52 mtn: committed revision a97e0433d041a6d253c5dc27e080d544e55d9c19
55 mtn: committed revision 2323d4bc324e6c82628dc04d47a9fd32ad24e322
53 56 % convert incrementally
54 57 assuming destination repo.mtn-hg
55 58 scanning source...
56 59 sorting...
57 60 converting...
58 61 5 update2 "with" quotes
59 62 4 createdir1
60 63 3 movedir1
61 64 2 movedir
62 65 1 emptydir
63 66 0 dropdirectory
64 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
67 6 files updated, 0 files merged, 0 files removed, 0 files unresolved
65 68 @ 7 "dropdirectory" files: dir2/dir/subdir/f
66 69 |
67 70 o 6 "emptydir" files: dir2/dir/subdir/f
68 71 |
69 o 5 "movedir" files: dir/a dir2/a
72 o 5 "movedir" files: dir/a dir/d dir2/a dir2/newfile
70 73 |
71 74 o 4 "movedir1" files: dir1/subdir1/file1 dir1/subdir2/file1
72 75 |
73 76 o 3 "createdir1" files: dir1/subdir1/file1 dir1/subdir2_other/file1
74 77 |
75 78 o 2 "update2 "with" quotes" files: bin bin2 dir/b e
76 79 |
77 80 o 1 "update1" files: a bin c dir/a dir/b
78 81 |
79 o 0 "initialize" files: a bin c dir/b
82 o 0 "initialize" files: a bin c dir/b dir/d
80 83
81 84 % manifest
82 85 bin2
83 86 dir1/subdir2/file1
84 87 dir1/subdir2_other/file1
85 88 dir2/a
89 dir2/newfile
86 90 e
87 91 % contents
88 92 a
89 93 a
90 94 % file move
91 95 copies: dir/a (a)
92 96 % check directory move
93 97 bin2
94 98 dir/a
99 dir/d
95 100 dir1/subdir2/file1
96 101 dir1/subdir2_other/file1
97 102 e
98 103 copies: dir1/subdir2/file1 (dir1/subdir1/file1)
104 % check file remove with directory move
105 bin2
106 dir1/subdir2/file1
107 dir1/subdir2_other/file1
108 dir2/a
109 dir2/newfile
110 e
General Comments 0
You need to be logged in to leave comments. Login now