Add a copyright command to display the copyright terms
[stgit] / stgit / git.py
index fe2c447..40bcd78 100644 (file)
@@ -75,10 +75,23 @@ class Commit:
     def get_committer(self):
         return self.__committer
 
     def get_committer(self):
         return self.__committer
 
+# dictionary of Commit objects, used to avoid multiple calls to git
+__commits = dict()
 
 #
 # Functions
 #
 
 #
 # Functions
 #
+def get_commit(id_hash):
+    """Commit objects factory. Save/look-up them in the __commits
+    dictionary
+    """
+    if id_hash in __commits:
+        return __commits[id_hash]
+    else:
+        commit = Commit(id_hash)
+        __commits[id_hash] = commit
+        return commit
+
 def get_conflicts():
     """Return the list of file conflicts
     """
 def get_conflicts():
     """Return the list of file conflicts
     """
@@ -94,7 +107,6 @@ def get_conflicts():
 def _input(cmd, file_desc):
     p = popen2.Popen3(cmd)
     for line in file_desc:
 def _input(cmd, file_desc):
     p = popen2.Popen3(cmd)
     for line in file_desc:
-        print line
         p.tochild.write(line)
     p.tochild.close()
     if p.wait():
         p.tochild.write(line)
     p.tochild.close()
     if p.wait():
@@ -143,7 +155,7 @@ def __run(cmd, args=None):
 def __check_base_dir():
     return os.path.isdir(base_dir)
 
 def __check_base_dir():
     return os.path.isdir(base_dir)
 
-def __tree_status(files = [], tree_id = 'HEAD', unknown = False):
+def __tree_status(files = [], tree_id = 'HEAD', unknown = False, noexclude = True):
     """Returns a list of pairs - [status, filename]
     """
     os.system('git-update-cache --refresh > /dev/null')
     """Returns a list of pairs - [status, filename]
     """
     os.system('git-update-cache --refresh > /dev/null')
@@ -153,13 +165,16 @@ def __tree_status(files = [], tree_id = 'HEAD', unknown = False):
     # unknown files
     if unknown:
         exclude_file = os.path.join(base_dir, 'exclude')
     # unknown files
     if unknown:
         exclude_file = os.path.join(base_dir, 'exclude')
+        base_exclude = [ '--exclude=*.[ao]', '--exclude=.*' '--exclude=TAGS',
+                        '--exclude=tags', '--exclude=*~', '--exclude=#*',
+                        '--exclude-per-directory=.gitignore' ]
         extra_exclude = []
         if os.path.exists(exclude_file):
             extra_exclude.append('--exclude-from=%s' % exclude_file)
         extra_exclude = []
         if os.path.exists(exclude_file):
             extra_exclude.append('--exclude-from=%s' % exclude_file)
-        lines = _output_lines(['git-ls-files', '--others',
-                        '--exclude=*.[ao]', '--exclude=.*'
-                        '--exclude=TAGS', '--exclude=tags', '--exclude=*~',
-                        '--exclude=#*'] + extra_exclude)
+        if noexclude:
+            extra_exclude = base_exclude = []
+        lines = _output_lines(['git-ls-files', '--others' ] + base_exclude
+                        + extra_exclude)
         cache_files += [('?', line.strip()) for line in lines]
 
     # conflicted files
         cache_files += [('?', line.strip()) for line in lines]
 
     # conflicted files
@@ -242,73 +257,54 @@ def rm(files, force = False):
         if files:
             __run('git-update-cache --force-remove --', files)
 
         if files:
             __run('git-update-cache --force-remove --', files)
 
-def update_cache(files):
+def update_cache(files = [], force = False):
     """Update the cache information for the given files
     """
     """Update the cache information for the given files
     """
-    files_here = []
-    files_gone = []
+    cache_files = __tree_status(files)
 
 
-    for f in files:
-        if os.path.exists(f):
-            files_here.append(f)
-        else:
-            files_gone.append(f)
+    # everything is up-to-date
+    if len(cache_files) == 0:
+        return False
+
+    # check for unresolved conflicts
+    if not force and [x for x in cache_files
+                      if x[0] not in ['M', 'N', 'A', 'D']]:
+        raise GitException, 'Updating cache failed: unresolved conflicts'
+
+    # update the cache
+    add_files = [x[1] for x in cache_files if x[0] in ['N', 'A']]
+    rm_files =  [x[1] for x in cache_files if x[0] in ['D']]
+    m_files =   [x[1] for x in cache_files if x[0] in ['M']]
+
+    if add_files and __run('git-update-cache --add --', add_files) != 0:
+        raise GitException, 'Failed git-update-cache --add'
+    if rm_files and __run('git-update-cache --force-remove --', rm_files) != 0:
+        raise GitException, 'Failed git-update-cache --rm'
+    if m_files and __run('git-update-cache --', m_files) != 0:
+        raise GitException, 'Failed git-update-cache'
 
 
-    if files_here:
-        __run('git-update-cache --', files_here)
-    if files_gone:
-        __run('git-update-cache --remove --', files_gone)
+    return True
 
 def commit(message, files = [], parents = [], allowempty = False,
 
 def commit(message, files = [], parents = [], allowempty = False,
+           cache_update = True,
            author_name = None, author_email = None, author_date = None,
            committer_name = None, committer_email = None):
     """Commit the current tree to repository
     """
            author_name = None, author_email = None, author_date = None,
            committer_name = None, committer_email = None):
     """Commit the current tree to repository
     """
-    first = (parents == [])
-
     # Get the tree status
     # Get the tree status
-    if not first:
-        cache_files = __tree_status(files)
-
-    if not first and len(cache_files) == 0 and not allowempty:
-        raise GitException, 'No changes to commit'
-
-    # check for unresolved conflicts
-    if not first and len(filter(lambda x: x[0] not in ['M', 'N', 'A', 'D'],
-                                cache_files)) != 0:
-        raise GitException, 'Commit failed: unresolved conflicts'
+    if cache_update and parents != []:
+        changes = update_cache(files)
+        if not changes and not allowempty:
+            raise GitException, 'No changes to commit'
 
     # get the commit message
     f = file('.commitmsg', 'w+')
 
     # get the commit message
     f = file('.commitmsg', 'w+')
-    if message[-1] == '\n':
+    if message[-1:] == '\n':
         f.write(message)
     else:
         print >> f, message
     f.close()
 
         f.write(message)
     else:
         print >> f, message
     f.close()
 
-    # update the cache
-    if not first:
-        add_files=[]
-        rm_files=[]
-        m_files=[]
-        for f in cache_files:
-            if f[0] in ['N', 'A']:
-                add_files.append(f[1])
-            elif f[0] == 'D':
-                rm_files.append(f[1])
-            else:
-                m_files.append(f[1])
-
-    if add_files:
-        if __run('git-update-cache --add --', add_files):
-            raise GitException, 'Failed git-update-cache --add'
-    if rm_files:
-        if __run('git-update-cache --force-remove --', rm_files):
-            raise GitException, 'Failed git-update-cache --rm'
-    if m_files:
-        if __run('git-update-cache --', m_files):
-            raise GitException, 'Failed git-update-cache'
-
     # write the index to repository
     tree_id = _output_one_line('git-write-tree')
 
     # write the index to repository
     tree_id = _output_one_line('git-write-tree')
 
@@ -349,15 +345,11 @@ def merge(base, head1, head2):
     if os.system('git-merge-cache -o gitmergeonefile.py -a') != 0:
         raise GitException, 'git-merge-cache failed (possible conflicts)'
 
     if os.system('git-merge-cache -o gitmergeonefile.py -a') != 0:
         raise GitException, 'git-merge-cache failed (possible conflicts)'
 
-    # this should not fail
-    if os.system('git-checkout-cache -f -a') != 0:
-        raise GitException, 'Failed git-checkout-cache'
-
 def status(files = [], modified = False, new = False, deleted = False,
 def status(files = [], modified = False, new = False, deleted = False,
-           conflict = False, unknown = False):
+           conflict = False, unknown = False, noexclude = False):
     """Show the tree status
     """
     """Show the tree status
     """
-    cache_files = __tree_status(files, unknown = True)
+    cache_files = __tree_status(files, unknown = True, noexclude = noexclude)
     all = not (modified or new or deleted or conflict or unknown)
 
     if not all:
     all = not (modified or new or deleted or conflict or unknown)
 
     if not all:
@@ -373,7 +365,7 @@ def status(files = [], modified = False, new = False, deleted = False,
             filestat.append('C')
         if unknown:
             filestat.append('?')
             filestat.append('C')
         if unknown:
             filestat.append('?')
-        cache_files = filter(lambda x: x[0] in filestat, cache_files)
+        cache_files = [x for x in cache_files if x[0] in filestat]
 
     for fs in cache_files:
         if all:
 
     for fs in cache_files:
         if all:
@@ -420,37 +412,48 @@ def files(rev1, rev2):
 
     return str.rstrip()
 
 
     return str.rstrip()
 
-def checkout(files = [], force = False):
+def checkout(files = [], tree_id = None, force = False):
     """Check out the given or all files
     """
     """Check out the given or all files
     """
-    git_flags = 'git-checkout-cache -q -u'
+    if tree_id and __run('git-read-tree -m', [tree_id]) != 0:
+        raise GitException, 'Failed git-read-tree -m %s' % tree_id
+
+    checkout_cmd = 'git-checkout-cache -q -u'
     if force:
     if force:
-        git_flags += ' -f'
+        checkout_cmd += ' -f'
     if len(files) == 0:
     if len(files) == 0:
-        git_flags += ' -a'
+        checkout_cmd += ' -a'
     else:
     else:
-        git_flags += ' --'
+        checkout_cmd += ' --'
 
 
-    if __run(git_flags, files) != 0:
+    if __run(checkout_cmd, files) != 0:
         raise GitException, 'Failed git-checkout-cache'
 
 def switch(tree_id):
     """Switch the tree to the given id
     """
         raise GitException, 'Failed git-checkout-cache'
 
 def switch(tree_id):
     """Switch the tree to the given id
     """
-    to_delete = filter(lambda x: x[0] in ['N', 'A'],
-                       __tree_status(tree_id = tree_id))
+    if __run('git-read-tree -u -m', [get_head(), tree_id]) != 0:
+        raise GitException, 'git-read-tree failed (local changes maybe?)'
 
 
-    if __run('git-read-tree -m', [tree_id]) != 0:
-        raise GitException, 'Failed git-read-tree -m %s' % tree_id
+    __set_head(tree_id)
+
+def reset(tree_id = None):
+    """Revert the tree changes relative to the given tree_id. It removes
+    any local changes
+    """
+    if not tree_id:
+        tree_id = get_head()
+
+    cache_files = __tree_status(tree_id = tree_id)
+    rm_files =  [x[1] for x in cache_files if x[0] in ['D']]
 
 
-    checkout(force = True)
+    checkout(tree_id = tree_id, force = True)
     __set_head(tree_id)
 
     # checkout doesn't remove files
     __set_head(tree_id)
 
     # checkout doesn't remove files
-    for fs in to_delete:
-        os.remove(fs[1])
+    map(os.remove, rm_files)
 
 
-def fetch(location, head = None, tag = None):
+def pull(location, head = None, tag = None):
     """Fetch changes from the remote repository. At the moment, just
     use the 'git fetch' scripts
     """
     """Fetch changes from the remote repository. At the moment, just
     use the 'git fetch' scripts
     """
@@ -460,11 +463,9 @@ def fetch(location, head = None, tag = None):
     elif tag:
         args += ['tag', tag]
 
     elif tag:
         args += ['tag', tag]
 
-    if __run('git fetch', args) != 0:
+    if __run('git pull', args) != 0:
         raise GitException, 'Failed "git fetch %s"' % location
 
         raise GitException, 'Failed "git fetch %s"' % location
 
-    return read_string(os.path.join(base_dir, 'FETCH_HEAD'))
-
 def apply_patch(filename = None):
     """Apply a patch onto the current index. There must not be any
     local changes in the tree, otherwise the command fails
 def apply_patch(filename = None):
     """Apply a patch onto the current index. There must not be any
     local changes in the tree, otherwise the command fails
@@ -476,3 +477,11 @@ def apply_patch(filename = None):
             raise GitException, 'Patch does not apply cleanly'
     else:
         _input('git-apply --index', sys.stdin)
             raise GitException, 'Patch does not apply cleanly'
     else:
         _input('git-apply --index', sys.stdin)
+
+def clone(repository, local_dir):
+    """Clone a remote repository. At the moment, just use the
+    'git clone' script
+    """
+    if __run('git clone', [repository, local_dir]) != 0:
+        raise GitException, 'Failed "git clone %s %s"' \
+              % (repository, local_dir)