hgext/purge/__init__.py
changeset 2379 e90cff87f871
parent 2378 6e5d40ec862d
child 2381 ab7a438294fc
equal deleted inserted replaced
2378:6e5d40ec862d 2379:e90cff87f871
       
     1 #!/usr/bin/env python
       
     2 #
       
     3 # Copyright (C) 2006 - Marco Barisione <marco@barisione.org>
       
     4 #
       
     5 # This is a small extension for Mercurial (http://www.selenic.com/mercurial)
       
     6 # that removes files not known to mercurial
       
     7 #
       
     8 # This program is free software; you can redistribute it and/or modify
       
     9 # it under the terms of the GNU General Public License as published by
       
    10 # the Free Software Foundation; either version 2 of the License, or
       
    11 # (at your option) any later version.
       
    12 #
       
    13 # This program is distributed in the hope that it will be useful,
       
    14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
       
    15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
       
    16 # GNU General Public License for more details.
       
    17 #
       
    18 # You should have received a copy of the GNU General Public License
       
    19 # along with this program; if not, write to the Free Software
       
    20 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
       
    21 
       
    22 from mercurial import hg, util
       
    23 import os
       
    24 
       
    25 def _(s):
       
    26     return s
       
    27 
       
    28 class Purge(object):
       
    29     def __init__(self, act=True, abort_on_err=False, eol='\n'):
       
    30         self._repo = None
       
    31         self._ui = None
       
    32         self._hg_root = None
       
    33         self._act = act
       
    34         self._abort_on_err = abort_on_err
       
    35         self._eol = eol
       
    36 
       
    37     def purge(self, ui, repo, dirs=None):
       
    38         self._repo = repo
       
    39         self._ui = ui
       
    40         self._hg_root = self._split_path(repo.root)
       
    41 
       
    42         if not dirs:
       
    43             dirs = [repo.root]
       
    44 
       
    45         for path in dirs:
       
    46             path = os.path.abspath(path)
       
    47             for root, dirs, files in os.walk(path, topdown=False):
       
    48                 if '.hg' in self._split_path(root):
       
    49                     # Skip files in the .hg directory.
       
    50                     # Note that if the repository is in a directory
       
    51                     # called .hg this command does not work.
       
    52                     continue
       
    53                 for name in files:
       
    54                     self._remove_file(os.path.join(root, name))
       
    55                 if not os.listdir(root):
       
    56                     # Remove this directory if it is empty.
       
    57                     self._remove_dir(root)
       
    58 
       
    59         self._repo = None
       
    60         self._ui = None
       
    61         self._hg_root = None
       
    62 
       
    63     def _error(self, msg):
       
    64         if self._abort_on_err:
       
    65             raise util.Abort(msg)
       
    66         else:
       
    67             self._ui.warn(_('warning: %s\n') % msg)
       
    68 
       
    69     def _remove_file(self, name):
       
    70         relative_name = self._relative_name(name)
       
    71         # dirstate.state() requires a path relative to the root
       
    72         # directory.
       
    73         if self._repo.dirstate.state(relative_name) != '?':
       
    74             return
       
    75         self._ui.note(_('Removing file %s\n') % name)
       
    76         if self._act:
       
    77             try:
       
    78                 os.remove(name)
       
    79             except OSError, e:
       
    80                 self._error(_('%s cannot be removed') % name)
       
    81         else:
       
    82             self._ui.write('%s%s' % (name, self._eol))
       
    83 
       
    84     def _remove_dir(self, name):
       
    85         self._ui.note(_('Removing directory %s\n') % name)
       
    86         if self._act:
       
    87             try:
       
    88                 os.rmdir(name)
       
    89             except OSError, e:
       
    90                 self._error(_('%s cannot be removed') % name)
       
    91         else:
       
    92             self._ui.write('%s%s' % (name, self._eol))
       
    93 
       
    94     def _relative_name(self, path):
       
    95         '''
       
    96         Returns "path" but relative to the root directory of the
       
    97         repository and with '\\' replaced with '/'.
       
    98         This is needed because this is the format required by
       
    99         self._repo.dirstate.state().
       
   100         '''
       
   101         splitted_path = self._split_path(path)[len(self._hg_root):]
       
   102         # Even on Windows self._repo.dirstate.state() wants '/'.
       
   103         return self._join_path(splitted_path).replace('\\', '/')
       
   104 
       
   105     def _split_path(self, path):
       
   106         '''
       
   107         Retruns a list of the single files/directories in "path".
       
   108         For instance:
       
   109           '/home/user/test' -> ['/', 'home', 'user', 'test']
       
   110           'C:\\Mercurial'   -> ['C:\\', 'Mercurial']
       
   111         '''
       
   112         ret = []
       
   113         while True:
       
   114             head, tail = os.path.split(path)
       
   115             if tail:
       
   116                 ret.append(tail)
       
   117             if head == path:
       
   118                 ret.append(head)
       
   119                 break
       
   120             path = head
       
   121         ret.reverse()
       
   122         return ret
       
   123 
       
   124     def _join_path(self, splitted_path):
       
   125         '''
       
   126         Joins a list returned by _split_path().
       
   127         '''
       
   128         ret = ''
       
   129         for part in splitted_path:
       
   130             if ret:
       
   131                 ret = os.path.join(ret, part)
       
   132             else:
       
   133                 ret = part
       
   134         return ret
       
   135 
       
   136 
       
   137 def purge(ui, repo, *dirs, **opts):
       
   138     '''removes files not tracked by mercurial
       
   139 
       
   140     Delete files not known to mercurial, this is useful to test local and
       
   141     uncommitted changes in the otherwise clean source tree.
       
   142 
       
   143     This means that purge will delete:
       
   144      - Unknown files: files marked with "?" by "hg status"
       
   145      - Ignored files: files usually ignored by Mercurial because they match
       
   146        a pattern in a ".hgignore" file
       
   147      - Empty directories: infact Mercurial ignores directories unless they
       
   148        contain files under source control managment
       
   149     But it will leave untouched:
       
   150      - Unmodified tracked files
       
   151      - Modified tracked files
       
   152      - New files added to the repository (with "hg add")
       
   153 
       
   154     If directories are given on the command line, only files in these
       
   155     directories are considered.
       
   156 
       
   157     Be careful with purge, you could irreversibly delete some files you
       
   158     forgot to add to the repository. If you only want to print the list of
       
   159     files that this program would delete use the -vn options.
       
   160     '''
       
   161     act = not opts['print']
       
   162     abort_on_err = bool(opts['abort_on_err'])
       
   163     eol = opts['print0'] and '\0' or '\n'
       
   164     if eol == '\0':
       
   165         # --print0 implies --print
       
   166         act = False
       
   167     p = Purge(act, abort_on_err, eol)
       
   168     p.purge(ui, repo, dirs)
       
   169 
       
   170 
       
   171 cmdtable = {
       
   172     'purge':    (purge,
       
   173                  [('a', 'abort-on-err', None, _('abort if an error occurs')),
       
   174                   ('p', 'print',        None, _('print the file names instead of deleting them')),
       
   175                   ('0', 'print0',       None, _('end filenames with NUL, for use with xargs (implies -p)')),
       
   176                  ],
       
   177                  _('hg purge [OPTIONS] [DIR]'))
       
   178 }