Executable flagging patch

Gustavo Niemeyer gustavo at niemeyer.net
Thu Sep 22 01:48:50 BST 2005


I'm sending attached the first version of the patch enabling
control of whether a file is executable or not by bzr. It
seems to work correctly in the tests I've done, but that's
still an early version, so any feedback is very welcome. Notice
that I have not yet implemented test cases, but no current tests
were broken by the new code (which could be considered a bad
sign as well :-).

Enjoy!

-- 
Gustavo Niemeyer
http://niemeyer.net
-------------- next part --------------
=== modified file 'bzrlib/builtins.py'
--- bzrlib/builtins.py
+++ bzrlib/builtins.py
@@ -626,7 +626,7 @@
         b = Branch.open_containing('.')
         td = compare_trees(b.basis_tree(), b.working_tree())
 
-        for path, id, kind in td.modified:
+        for path, id, kind, text_modified, meta_modified in td.modified:
             print path
 
 

=== modified file 'bzrlib/changeset.py'
--- bzrlib/changeset.py
+++ bzrlib/changeset.py
@@ -1384,18 +1384,20 @@
 
         if cs_entry is None:
             return None
+
+        full_path_a = self.tree_a.readonly_path(id)
+        full_path_b = self.tree_b.readonly_path(id)
+        stat_a = self.lstat(full_path_a)
+        stat_b = self.lstat(full_path_b)
+
+        cs_entry.metadata_change = self.make_mode_change(stat_a, stat_b)
+
         if id in self.tree_a and id in self.tree_b:
             a_sha1 = self.tree_a.get_file_sha1(id)
             b_sha1 = self.tree_b.get_file_sha1(id)
             if None not in (a_sha1, b_sha1) and a_sha1 == b_sha1:
                 return cs_entry
 
-        full_path_a = self.tree_a.readonly_path(id)
-        full_path_b = self.tree_b.readonly_path(id)
-        stat_a = self.lstat(full_path_a)
-        stat_b = self.lstat(full_path_b)
-        
-        cs_entry.metadata_change = self.make_mode_change(stat_a, stat_b)
         cs_entry.contents_change = self.make_contents_change(full_path_a,
                                                              stat_a, 
                                                              full_path_b, 

=== modified file 'bzrlib/commit.py'
--- bzrlib/commit.py
+++ bzrlib/commit.py
@@ -312,6 +312,8 @@
                 branch.text_store.add(content, entry.text_id)
                 mutter('    stored with text_id {%s}' % entry.text_id)
 
+            entry.executable = work_tree.is_executable(file_id)
+
         if verbose:
             marked = path + kind_marker(entry.kind)
             if not old_ie:

=== modified file 'bzrlib/delta.py'
--- bzrlib/delta.py
+++ bzrlib/delta.py
@@ -26,9 +26,9 @@
     removed
         (path, id, kind)
     renamed
-        (oldpath, newpath, id, kind, text_modified)
+        (oldpath, newpath, id, kind, text_modified, meta_modified)
     modified
-        (path, id, kind)
+        (path, id, kind, text_modified, meta_modified)
     unchanged
         (path, id, kind)
 
@@ -86,12 +86,17 @@
 
     def show(self, to_file, show_ids=False, show_unchanged=False):
         def show_list(files):
-            for path, fid, kind in files:
+            for item in files:
+                path, fid, kind = item[:3]
+
                 if kind == 'directory':
                     path += '/'
                 elif kind == 'symlink':
                     path += '@'
-                    
+
+                if len(item) == 5 and item[4]:
+                    path += '*'
+
                 if show_ids:
                     print >>to_file, '  %-30s %s' % (path, fid)
                 else:
@@ -107,7 +112,10 @@
 
         if self.renamed:
             print >>to_file, 'renamed:'
-            for oldpath, newpath, fid, kind, text_modified in self.renamed:
+            for (oldpath, newpath, fid, kind,
+                 text_modified, meta_modified) in self.renamed:
+                if meta_modified:
+                    newpath += '*'
                 if show_ids:
                     print >>to_file, '  %s => %s %s' % (oldpath, newpath, fid)
                 else:
@@ -175,9 +183,15 @@
                 old_sha1 = old_tree.get_file_sha1(file_id)
                 new_sha1 = new_tree.get_file_sha1(file_id)
                 text_modified = (old_sha1 != new_sha1)
+
+                old_exec = old_tree.is_executable(file_id)
+                new_exec = new_tree.is_executable(file_id)
+                meta_modified = (old_exec != new_exec)
             else:
                 ## mutter("no text to check for %r %r" % (file_id, kind))
                 text_modified = False
+                meta_modified = False
+                
 
             # TODO: Can possibly avoid calculating path strings if the
             # two files are unchanged and their names and parents are
@@ -189,9 +203,10 @@
                 delta.renamed.append((old_inv.id2path(file_id),
                                       new_inv.id2path(file_id),
                                       file_id, kind,
-                                      text_modified))
-            elif text_modified:
-                delta.modified.append((new_inv.id2path(file_id), file_id, kind))
+                                      text_modified, meta_modified))
+            elif text_modified or meta_modified:
+                delta.modified.append((new_inv.id2path(file_id), file_id, kind,
+                                       text_modified, meta_modified))
             elif want_unchanged:
                 delta.unchanged.append((new_inv.id2path(file_id), file_id, kind))
         else:

=== modified file 'bzrlib/diff.py'
--- bzrlib/diff.py
+++ bzrlib/diff.py
@@ -229,8 +229,14 @@
                       new_tree.get_file(file_id).readlines(),
                       to_file)
 
-    for old_path, new_path, file_id, kind, text_modified in delta.renamed:
-        print >>to_file, '=== renamed %s %r => %r' % (kind, old_path, new_path)
+    for (old_path, new_path, file_id, kind,
+         text_modified, meta_modified) in delta.renamed:
+        if meta_modified:
+            prop_str = " (properties changed)"
+        else:
+            prop_str = ""
+        print >>to_file, '=== renamed %s %r => %r%s' % \
+                         (kind, old_path, new_path, prop_str)
         if text_modified:
             diff_file(old_label + old_path,
                       old_tree.get_file(file_id).readlines(),
@@ -238,9 +244,13 @@
                       new_tree.get_file(file_id).readlines(),
                       to_file)
 
-    for path, file_id, kind in delta.modified:
-        print >>to_file, '=== modified %s %r' % (kind, path)
-        if kind == 'file':
+    for path, file_id, kind, text_modified, meta_modified in delta.modified:
+        if meta_modified:
+            prop_str = " (properties changed)"
+        else:
+            prop_str = ""
+        print >>to_file, '=== modified %s %r%s' % (kind, path, prop_str)
+        if text_modified:
             diff_file(old_label + path,
                       old_tree.get_file(file_id).readlines(),
                       new_label + path,

=== modified file 'bzrlib/inventory.py'
--- bzrlib/inventory.py
+++ bzrlib/inventory.py
@@ -44,6 +44,7 @@
     * *text_sha1*: only for files
     * *text_size*: in bytes, only for files 
     * *text_id*: identifier for the text version, only for files
+    * *executable*: only for files
 
     InventoryEntries can also exist inside a WorkingTree
     inventory, in which case they are not yet bound to a
@@ -101,7 +102,7 @@
 
     __slots__ = ['text_sha1', 'text_size', 'file_id', 'name', 'kind',
                  'text_id', 'parent_id', 'children',
-                 'text_version', 'entry_version', ]
+                 'text_version', 'entry_version', 'executable']
 
 
     def __init__(self, file_id, name, kind, parent_id, text_id=None):
@@ -132,6 +133,7 @@
         self.kind = kind
         self.text_id = text_id
         self.parent_id = parent_id
+        self.executable = False
         if kind == 'directory':
             self.children = {}
         elif kind == 'file':
@@ -178,7 +180,8 @@
                and (self.parent_id == other.parent_id) \
                and (self.kind == other.kind) \
                and (self.text_version == other.text_version) \
-               and (self.entry_version == other.entry_version)
+               and (self.entry_version == other.entry_version) \
+               and (self.executable == other.executable)
 
 
     def __ne__(self, other):

=== modified file 'bzrlib/merge.py'
--- bzrlib/merge.py
+++ bzrlib/merge.py
@@ -186,6 +186,9 @@
     def get_file_sha1(self, id):
         return self.tree.get_file_sha1(id)
 
+    def is_executable(self, id):
+        return self.tree.is_executable(id)
+
     def id2path(self, file_id):
         return self.tree.id2path(file_id)
 
@@ -214,6 +217,8 @@
                 path = os.path.join(self.tempdir, "texts", id)
                 outfile = file(path, "wb")
                 outfile.write(self.tree.get_file(id).read())
+                if self.tree.is_executable(id):
+                    os.chmod(path, 0755)
                 assert(os.path.exists(path))
                 self.cached[id] = path
             return self.cached[id]
@@ -348,7 +353,7 @@
         return tree.tree.inventory
 
     inv_changes = merge_flex(this_tree, base_tree, other_tree,
-                             generate_cset_optimized, get_inventory,
+                             generate_changeset, get_inventory,
                              MergeConflictHandler(ignore_zero=ignore_zero),
                              merge_factory=merge_factory, 
                              interesting_ids=interesting_ids)

=== modified file 'bzrlib/merge_core.py'
--- bzrlib/merge_core.py
+++ bzrlib/merge_core.py
@@ -246,10 +246,18 @@
             raise Exception("Unhandled merge scenario")
 
 def make_merged_metadata(entry, base, other):
-    if entry.metadata_change is not None:
-        base_path = base.readonly_path(entry.id)
-        other_path = other.readonly_path(entry.id)    
-        return PermissionsMerge(base_path, other_path)
+    metadata = entry.metadata_change
+    if metadata is None:
+        return None
+    if isinstance(metadata, changeset.ChangeUnixPermissions):
+        if metadata.new_mode is None:
+            return None
+        elif metadata.old_mode is None:
+            return metadata
+        else:
+            base_path = base.readonly_path(entry.id)
+            other_path = other.readonly_path(entry.id)    
+            return PermissionsMerge(base_path, other_path)
     
 
 class PermissionsMerge(object):

=== modified file 'bzrlib/tree.py'
--- bzrlib/tree.py
+++ bzrlib/tree.py
@@ -137,6 +137,9 @@
         ie = self._inventory[file_id]
         if ie.kind == "file":
             return ie.text_sha1
+
+    def is_executable(self, file_id):
+        return self._inventory[file_id].executable
 
     def has_filename(self, filename):
         return bool(self.inventory.path2id(filename))

=== modified file 'bzrlib/workingtree.py'
--- bzrlib/workingtree.py
+++ bzrlib/workingtree.py
@@ -21,6 +21,7 @@
 # it's not predictable when it will be written out.
 
 import os
+import stat
 import fnmatch
         
 import bzrlib.tree
@@ -116,6 +117,15 @@
     def get_file_sha1(self, file_id):
         path = self._inventory.id2path(file_id)
         return self._hashcache.get_sha1(path)
+
+
+    def is_executable(self, file_id):
+        if os.name == "nt":
+            return self._inventory[file_id].executable
+        else:
+            path = self._inventory.id2path(file_id)
+            mode = os.lstat(self.abspath(path)).st_mode
+            return bool(stat.S_ISREG(mode) and stat.S_IEXEC&mode)
 
 
     def file_class(self, filename):

=== modified file 'bzrlib/xml.py'
--- bzrlib/xml.py
+++ bzrlib/xml.py
@@ -84,6 +84,9 @@
 
         if ie.text_size != None:
             e.set('text_size', '%d' % ie.text_size)
+
+        if ie.executable:
+            e.set('executable', 'yes')
 
         for f in ['text_id', 'text_sha1']:
             v = getattr(ie, f)
@@ -133,6 +136,9 @@
         ie.text_id = elt.get('text_id')
         ie.text_sha1 = elt.get('text_sha1')
 
+        if elt.get('executable') == 'yes':
+            ie.executable = True
+
         ## mutter("read inventoryentry: %r" % (elt.attrib))
 
         v = elt.get('text_size')



More information about the bazaar mailing list