677
by Martin Pool
- draft 'meta' command by john |
1 |
*** modified file 'bzrlib/commands.py' |
2 |
--- bzrlib/commands.py
|
|
3 |
+++ bzrlib/commands.py
|
|
4 |
@@ -1175,7 +1175,68 @@
|
|
5 |
b = Branch('.') |
|
6 |
statcache.update_cache(b.base, b.read_working_inventory()) |
|
7 |
||
8 |
-
|
|
9 |
+class cmd_meta(Command):
|
|
10 |
+ """Get or set the meta information properties about a file.
|
|
11 |
+
|
|
12 |
+ bzr meta FILENAME # Display all meta information
|
|
13 |
+ bzr meta FILENAME prop # Display the value of meta-info named prop, return error if not found
|
|
14 |
+ bzr meta FILENAME --set prop value # Set the value of prop to value
|
|
15 |
+ echo "the value" | bzr meta FILENAME --set prop # Set the value to whatever is read from stdin
|
|
16 |
+ bzr meta FILENAME --unset prop # Remove the property
|
|
17 |
+
|
|
18 |
+ bzr meta FILENAME --revision=10 # Display the meta information for a given revision
|
|
19 |
+ (Not supported yet)
|
|
20 |
+ """
|
|
21 |
+ hidden = True
|
|
22 |
+ takes_args = ['filename', 'property?', 'value?']
|
|
23 |
+ takes_options = ['revision', 'set', 'unset']
|
|
24 |
+
|
|
25 |
+ def run(self, filename, property=None, value=None, revision=None, set=False, unset=False):
|
|
26 |
+ if isinstance(revision, list) and len(revision) > 1:
|
|
27 |
+ raise BzrCommandError('bzr meta takes at most 1 revision.')
|
|
28 |
+ if revision is not None and (set or unset):
|
|
29 |
+ raise BzrCommandError('Cannot set/unset meta information in an old version.')
|
|
30 |
+ if set and unset:
|
|
31 |
+ raise BzrCommandError('Cannot set and unset at the same time')
|
|
32 |
+ if not set and value:
|
|
33 |
+ raise BzrCommandError('You must supply --set if you want to set the value of a property.')
|
|
34 |
+
|
|
35 |
+ b = Branch(filename)
|
|
36 |
+ inv = b.inventory
|
|
37 |
+ file_id = inv.path2id(b.relpath(filename))
|
|
38 |
+ inv_entry = inv[file_id]
|
|
39 |
+ if not property:
|
|
40 |
+ meta = inv_entry.meta
|
|
41 |
+ if meta: # Not having meta is the same as having an empty meta
|
|
42 |
+ keys = meta.properties.keys()
|
|
43 |
+ keys.sort()
|
|
44 |
+ # The output really needs to be parseable
|
|
45 |
+ for key in keys:
|
|
46 |
+ print '%s: %r' % (key, meta.properties[key])
|
|
47 |
+ else:
|
|
48 |
+ meta = inv_entry.meta
|
|
49 |
+ if set:
|
|
50 |
+ if value is None:
|
|
51 |
+ value = sys.stdin.read()
|
|
52 |
+ if not meta:
|
|
53 |
+ from bzrlib.inventory import Meta
|
|
54 |
+ inv_entry.meta = Meta({property:value})
|
|
55 |
+ else:
|
|
56 |
+ inv_entry.meta.properties[property] = value
|
|
57 |
+ b.inventory = inv # This should cause it to be saved
|
|
58 |
+ elif unset:
|
|
59 |
+ if not meta or not meta.properties.has_key(property):
|
|
60 |
+ return 3 # Cannot unset a property that doesn't exist
|
|
61 |
+ # I wonder if this should be a different return code
|
|
62 |
+ del inv_entry.meta.properties[property]
|
|
63 |
+ b.inventory = inv
|
|
64 |
+ else:
|
|
65 |
+ if not meta or not meta.properties.has_key(property):
|
|
66 |
+ return 3 # Missing property
|
|
67 |
+ # Probably this should not be print, but
|
|
68 |
+ # sys.stdout.write() so that you get back exactly
|
|
69 |
+ # what was given. But I'm leaving it this way for now
|
|
70 |
+ print meta.properties[property]
|
|
71 |
||
72 |
# list of all available options; the rhs can be either None for an |
|
73 |
# option that takes no argument, or a constructor function that checks |
|
74 |
@@ -1196,6 +1257,8 @@
|
|
75 |
'verbose': None, |
|
76 |
'version': None, |
|
77 |
'email': None, |
|
78 |
+ 'set': None,
|
|
79 |
+ 'unset': None,
|
|
80 |
} |
|
81 |
||
82 |
SHORT_OPTIONS = { |
|
83 |
||
84 |
*** modified file 'bzrlib/diff.py' |
|
85 |
--- bzrlib/diff.py
|
|
86 |
+++ bzrlib/diff.py
|
|
87 |
@@ -226,8 +226,10 @@
|
|
88 |
new_tree.get_file(file_id).readlines(), |
|
89 |
to_file) |
|
90 |
||
91 |
- for old_path, new_path, file_id, kind, text_modified in delta.renamed:
|
|
92 |
+ for old_path, new_path, file_id, kind, text_modified, meta_modified in delta.renamed:
|
|
93 |
print '*** renamed %s %r => %r' % (kind, old_path, new_path) |
|
94 |
+ if meta_modified:
|
|
95 |
+ print '## Meta-info modified'
|
|
96 |
if text_modified: |
|
97 |
diff_file(old_label + old_path, |
|
98 |
old_tree.get_file(file_id).readlines(), |
|
99 |
@@ -235,9 +237,11 @@
|
|
100 |
new_tree.get_file(file_id).readlines(), |
|
101 |
to_file) |
|
102 |
||
103 |
- for path, file_id, kind in delta.modified:
|
|
104 |
+ for path, file_id, kind, text_modified, meta_modified in delta.modified:
|
|
105 |
print '*** modified %s %r' % (kind, path) |
|
106 |
- if kind == 'file':
|
|
107 |
+ if meta_modified:
|
|
108 |
+ print '## Meta-info modified'
|
|
109 |
+ if kind == 'file' and text_modified:
|
|
110 |
diff_file(old_label + path, |
|
111 |
old_tree.get_file(file_id).readlines(), |
|
112 |
new_label + path, |
|
113 |
@@ -316,7 +320,7 @@
|
|
114 |
||
115 |
if self.renamed: |
|
116 |
print >>to_file, 'renamed:' |
|
117 |
- for oldpath, newpath, fid, kind, text_modified in self.renamed:
|
|
118 |
+ for oldpath, newpath, fid, kind, text_modified, meta_modified in self.renamed:
|
|
119 |
if show_ids: |
|
120 |
print >>to_file, ' %s => %s %s' % (oldpath, newpath, fid) |
|
121 |
else: |
|
122 |
@@ -324,7 +328,16 @@
|
|
123 |
||
124 |
if self.modified: |
|
125 |
print >>to_file, 'modified:' |
|
126 |
- show_list(self.modified)
|
|
127 |
+ for path, fid, kind, text_modified, meta_modified in self.modified:
|
|
128 |
+ if kind == 'directory':
|
|
129 |
+ path += '/'
|
|
130 |
+ elif kind == 'symlink':
|
|
131 |
+ path += '@'
|
|
132 |
+
|
|
133 |
+ if show_ids:
|
|
134 |
+ print >>to_file, ' %-30s %s' % (path, fid)
|
|
135 |
+ else:
|
|
136 |
+ print >>to_file, ' ', path
|
|
137 |
||
138 |
if show_unchanged and self.unchanged: |
|
139 |
print >>to_file, 'unchanged:' |
|
140 |
@@ -388,6 +401,14 @@
|
|
141 |
## mutter("no text to check for %r %r" % (file_id, kind)) |
|
142 |
text_modified = False |
|
143 |
||
144 |
+ old_meta = old_inv[file_id].meta
|
|
145 |
+ new_meta = new_inv[file_id].meta
|
|
146 |
+
|
|
147 |
+ if old_meta != new_meta:
|
|
148 |
+ meta_modified = True
|
|
149 |
+ else:
|
|
150 |
+ meta_modified = False
|
|
151 |
+
|
|
152 |
# TODO: Can possibly avoid calculating path strings if the |
|
153 |
# two files are unchanged and their names and parents are |
|
154 |
# the same and the parents are unchanged all the way up. |
|
155 |
@@ -395,9 +416,10 @@
|
|
156 |
||
157 |
if old_path != new_path: |
|
158 |
delta.renamed.append((old_path, new_path, file_id, kind, |
|
159 |
- text_modified))
|
|
160 |
- elif text_modified:
|
|
161 |
- delta.modified.append((new_path, file_id, kind))
|
|
162 |
+ text_modified, meta_modified))
|
|
163 |
+ elif text_modified or meta_modified:
|
|
164 |
+ delta.modified.append((new_path, file_id, kind,
|
|
165 |
+ text_modified, meta_modified))
|
|
166 |
elif want_unchanged: |
|
167 |
delta.unchanged.append((new_path, file_id, kind)) |
|
168 |
else: |
|
169 |
||
170 |
*** modified file 'bzrlib/inventory.py' |
|
171 |
--- bzrlib/inventory.py
|
|
172 |
+++ bzrlib/inventory.py
|
|
173 |
@@ -33,6 +33,67 @@
|
|
174 |
import bzrlib |
|
175 |
from bzrlib.osutils import uuid, quotefn, splitpath, joinpath, appendpath |
|
176 |
from bzrlib.trace import mutter |
|
177 |
+
|
|
178 |
+class Meta(XMLMixin):
|
|
179 |
+ """Meta information about a single inventory entry.
|
|
180 |
+
|
|
181 |
+ This is basically just a set of key->value pairs.
|
|
182 |
+
|
|
183 |
+ In general, bzr does not handle this information, it only provides
|
|
184 |
+ a location for plugins and other data sources to store this data.
|
|
185 |
+
|
|
186 |
+ """
|
|
187 |
+ def __init__(self, properties):
|
|
188 |
+ self.properties = properties
|
|
189 |
+
|
|
190 |
+ def __eq__(self, other):
|
|
191 |
+ if other is None:
|
|
192 |
+ return False
|
|
193 |
+ if not isinstance(other, Meta):
|
|
194 |
+ return NotImplemented
|
|
195 |
+
|
|
196 |
+ return (self.properties == other.properties)
|
|
197 |
+
|
|
198 |
+ def __ne__(self, other):
|
|
199 |
+ return not (self == other)
|
|
200 |
+
|
|
201 |
+ def __hash__(self):
|
|
202 |
+ raise ValueError('not hashable')
|
|
203 |
+
|
|
204 |
+ def from_element(cls, elt):
|
|
205 |
+ assert elt.tag == 'meta'
|
|
206 |
+
|
|
207 |
+ properties = {}
|
|
208 |
+ for child in elt:
|
|
209 |
+ if child.tag == 'property':
|
|
210 |
+ if child.text is None:
|
|
211 |
+ properties[child.get('name')] = ''
|
|
212 |
+ else:
|
|
213 |
+ properties[child.get('name')] = child.text
|
|
214 |
+ self = cls(properties)
|
|
215 |
+ return self
|
|
216 |
+
|
|
217 |
+ from_element = classmethod(from_element)
|
|
218 |
+
|
|
219 |
+ def to_element(self):
|
|
220 |
+ e = Element('meta')
|
|
221 |
+ keys = self.properties.keys()
|
|
222 |
+ keys.sort()
|
|
223 |
+
|
|
224 |
+ # The blah.text is just to make things look pretty in the files
|
|
225 |
+ # We may want to remove it
|
|
226 |
+ if len(keys) > 0:
|
|
227 |
+ e.text = '\n'
|
|
228 |
+
|
|
229 |
+ for key in keys:
|
|
230 |
+ prop = Element('property')
|
|
231 |
+ prop.set('name', key)
|
|
232 |
+ prop.text=self.properties[key]
|
|
233 |
+ prop.tail='\n'
|
|
234 |
+ e.append(prop)
|
|
235 |
+
|
|
236 |
+ return e
|
|
237 |
+
|
|
238 |
||
239 |
class InventoryEntry(XMLMixin): |
|
240 |
"""Description of a versioned file. |
|
241 |
@@ -100,6 +161,7 @@
|
|
242 |
||
243 |
text_sha1 = None |
|
244 |
text_size = None |
|
245 |
+ meta = None
|
|
246 |
||
247 |
def __init__(self, file_id, name, kind, parent_id, text_id=None): |
|
248 |
"""Create an InventoryEntry |
|
249 |
@@ -124,6 +186,7 @@
|
|
250 |
self.kind = kind |
|
251 |
self.text_id = text_id |
|
252 |
self.parent_id = parent_id |
|
253 |
+ self.meta = None
|
|
254 |
if kind == 'directory': |
|
255 |
self.children = {} |
|
256 |
elif kind == 'file': |
|
257 |
@@ -146,6 +209,10 @@
|
|
258 |
other.text_size = self.text_size |
|
259 |
# note that children are *not* copied; they're pulled across when |
|
260 |
# others are added |
|
261 |
+ if self.meta:
|
|
262 |
+ other.meta = Meta(self.meta.properties)
|
|
263 |
+ else:
|
|
264 |
+ other.meta = None
|
|
265 |
return other |
|
266 |
||
267 |
||
268 |
@@ -182,6 +249,9 @@
|
|
269 |
e.set('parent_id', self.parent_id) |
|
270 |
||
271 |
e.tail = '\n' |
|
272 |
+
|
|
273 |
+ if self.meta:
|
|
274 |
+ e.append(self.meta.to_element())
|
|
275 |
||
276 |
return e |
|
277 |
||
278 |
@@ -205,6 +275,11 @@
|
|
279 |
v = elt.get('text_size') |
|
280 |
self.text_size = v and int(v) |
|
281 |
||
282 |
+ self.meta = None
|
|
283 |
+ for child in elt:
|
|
284 |
+ if child.tag == 'meta':
|
|
285 |
+ self.meta = Meta.from_element(child)
|
|
286 |
+
|
|
287 |
return self |
|
288 |
||
289 |
||
290 |
@@ -220,7 +295,8 @@
|
|
291 |
and (self.text_size == other.text_size) \ |
|
292 |
and (self.text_id == other.text_id) \ |
|
293 |
and (self.parent_id == other.parent_id) \ |
|
294 |
- and (self.kind == other.kind)
|
|
295 |
+ and (self.kind == other.kind) \
|
|
296 |
+ and (self.meta == other.meta)
|
|
297 |
||
298 |
||
299 |
def __ne__(self, other): |
|
300 |