Add a merged upstream test for pull and push
[stgit] / stgit / git.py
index 9a07fa5..40d54ef 100644 (file)
@@ -27,13 +27,6 @@ class GitException(Exception):
     pass
 
 
-# Different start-up variables read from the environment
-if 'GIT_DIR' in os.environ:
-    base_dir = os.environ['GIT_DIR']
-else:
-    base_dir = '.git'
-
-head_link = os.path.join(base_dir, 'HEAD')
 
 #
 # Classes
@@ -88,6 +81,15 @@ __commits = dict()
 #
 # Functions
 #
+
+def get_base_dir():
+    """Different start-up variables read from the environment
+    """
+    if 'GIT_DIR' in os.environ:
+        return os.environ['GIT_DIR']
+    else:
+        return _output_one_line('git-rev-parse --git-dir')
+
 def get_commit(id_hash):
     """Commit objects factory. Save/look-up them in the __commits
     dictionary
@@ -104,7 +106,7 @@ def get_commit(id_hash):
 def get_conflicts():
     """Return the list of file conflicts
     """
-    conflicts_file = os.path.join(base_dir, 'conflicts')
+    conflicts_file = os.path.join(get_base_dir(), 'conflicts')
     if os.path.isfile(conflicts_file):
         f = file(conflicts_file)
         names = [line.strip() for line in f.readlines()]
@@ -168,9 +170,6 @@ def __run(cmd, args=None):
         return r
     return 0
 
-def __check_base_dir():
-    return os.path.isdir(base_dir)
-
 def __tree_status(files = None, tree_id = 'HEAD', unknown = False,
                   noexclude = True):
     """Returns a list of pairs - [status, filename]
@@ -183,7 +182,7 @@ def __tree_status(files = None, tree_id = 'HEAD', unknown = False,
 
     # unknown files
     if unknown:
-        exclude_file = os.path.join(base_dir, 'info', 'exclude')
+        exclude_file = os.path.join(get_base_dir(), 'info', 'exclude')
         base_exclude = ['--exclude=%s' % s for s in
                         ['*.[ao]', '*.pyc', '.*', '*~', '#*', 'TAGS', 'tags']]
         base_exclude.append('--exclude-per-directory=.gitignore')
@@ -206,7 +205,7 @@ def __tree_status(files = None, tree_id = 'HEAD', unknown = False,
     cache_files += [('C', filename) for filename in conflicts]
 
     # the rest
-    for line in _output_lines(['git-diff-index', '-r', tree_id] + files):
+    for line in _output_lines(['git-diff-index', tree_id] + files):
         fs = tuple(line.rstrip().split(' ',4)[-1].split('\t',1))
         if fs[1] not in conflicts:
             cache_files.append(fs)
@@ -254,6 +253,9 @@ def __set_head(val):
             raise GitException, 'Could not update HEAD to "%s".' % val
         __head = val
 
+    # only allow SHA1 hashes
+    assert(len(__head) == 40)
+
 def __clear_head_cache():
     """Sets the __head to None so that a re-read is forced
     """
@@ -275,7 +277,7 @@ def rev_parse(git_id):
         raise GitException, 'Unknown revision: %s' % git_id
 
 def branch_exists(branch):
-    """Existance check for the named branch
+    """Existence check for the named branch
     """
     for line in _output_lines(['git-rev-parse', '--symbolic', '--all']):
         if line.strip() == branch:
@@ -297,8 +299,8 @@ def create_branch(new_branch, tree_id = None):
     if tree_id:
         switch(tree_id)
 
-    if os.path.isfile(os.path.join(base_dir, 'MERGE_HEAD')):
-        os.remove(os.path.join(base_dir, 'MERGE_HEAD'))
+    if os.path.isfile(os.path.join(get_base_dir(), 'MERGE_HEAD')):
+        os.remove(os.path.join(get_base_dir(), 'MERGE_HEAD'))
 
 def switch_branch(name):
     """Switch to a git branch
@@ -309,7 +311,7 @@ def switch_branch(name):
     if not branch_exists(new_head):
         raise GitException, 'Branch "%s" does not exist' % name
 
-    tree_id = rev_parse(new_head + '^0')
+    tree_id = rev_parse(new_head + '^{commit}')
     if tree_id != get_head():
         refresh_index()
         if __run('git-read-tree -u -m', [get_head(), tree_id]) != 0:
@@ -317,8 +319,8 @@ def switch_branch(name):
         __head = tree_id
     set_head_file(new_head)
 
-    if os.path.isfile(os.path.join(base_dir, 'MERGE_HEAD')):
-        os.remove(os.path.join(base_dir, 'MERGE_HEAD'))
+    if os.path.isfile(os.path.join(get_base_dir(), 'MERGE_HEAD')):
+        os.remove(os.path.join(get_base_dir(), 'MERGE_HEAD'))
 
 def delete_branch(name):
     """Delete a git branch
@@ -326,7 +328,7 @@ def delete_branch(name):
     branch_head = os.path.join('refs', 'heads', name)
     if not branch_exists(branch_head):
         raise GitException, 'Branch "%s" does not exist' % name
-    os.remove(os.path.join(base_dir, branch_head))
+    os.remove(os.path.join(get_base_dir(), branch_head))
 
 def rename_branch(from_name, to_name):
     """Rename a git branch
@@ -340,7 +342,8 @@ def rename_branch(from_name, to_name):
 
     if get_head_file() == from_name:
         set_head_file(to_head)
-    os.rename(os.path.join(base_dir, from_head), os.path.join(base_dir, to_head))
+    os.rename(os.path.join(get_base_dir(), from_head), \
+              os.path.join(get_base_dir(), to_head))
 
 def add(names):
     """Add the files or recursively add the directory contents
@@ -462,14 +465,20 @@ def commit(message, files = None, parents = None, allowempty = False,
 
     return commit_id
 
-def apply_diff(rev1, rev2):
+def apply_diff(rev1, rev2, check_index = True):
     """Apply the diff between rev1 and rev2 onto the current
     index. This function doesn't need to raise an exception since it
     is only used for fast-pushing a patch. If this operation fails,
     the pushing would fall back to the three-way merge.
     """
-    return os.system('git-diff-tree -p %s %s | git-apply --index 2> /dev/null'
-                     % (rev1, rev2)) == 0
+    if check_index:
+        index_opt = '--index'
+    else:
+        index_opt = ''
+    cmd = 'git-diff-tree -p %s %s | git-apply %s 2> /dev/null' \
+          % (rev1, rev2, index_opt)
+
+    return os.system(cmd) == 0
 
 def merge(base, head1, head2):
     """Perform a 3-way merge between base, head1 and head2 into the
@@ -520,11 +529,16 @@ def diff(files = None, rev1 = 'HEAD', rev2 = None, out_fd = None):
     if not files:
         files = []
 
-    if rev2:
+    if rev1 and rev2:
         diff_str = _output(['git-diff-tree', '-p', rev1, rev2] + files)
-    else:
+    elif rev1 or rev2:
         refresh_index()
-        diff_str = _output(['git-diff-index', '-p', rev1] + files)
+        if rev2:
+            diff_str = _output(['git-diff-index', '-p', '-R', rev2] + files)
+        else:
+            diff_str = _output(['git-diff-index', '-p', rev1] + files)
+    else:
+        diff_str = ''
 
     if out_fd:
         out_fd.write(diff_str)
@@ -594,21 +608,18 @@ def switch(tree_id):
 
     __set_head(tree_id)
 
-def reset(tree_id = None):
+def reset(files = None, 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(tree_id = tree_id, force = True)
-    __set_head(tree_id)
+    checkout(files, tree_id, True)
 
-    # checkout doesn't remove files
-    map(os.remove, rm_files)
+    # if the reset refers to the whole tree, switch the HEAD as well
+    if not files:
+        __set_head(tree_id)
 
 def pull(repository = 'origin', refspec = None):
     """Pull changes from the remote repository. At the moment, just