import sys, os, popen2, re, gitmergeonefile
+from stgit import basedir
from stgit.utils import *
# git exception class
# Functions
#
-# GIT_DIR value cached
-__base_dir = None
-
-def get_base_dir():
- """Different start-up variables read from the environment
- """
- global __base_dir
-
- if not __base_dir:
- if 'GIT_DIR' in os.environ:
- __base_dir = os.environ['GIT_DIR']
- else:
- __base_dir = _output_one_line('git-rev-parse --git-dir')
-
- return __base_dir
-
def get_commit(id_hash):
"""Commit objects factory. Save/look-up them in the __commits
dictionary
def get_conflicts():
"""Return the list of file conflicts
"""
- conflicts_file = os.path.join(get_base_dir(), 'conflicts')
+ conflicts_file = os.path.join(basedir.get(), 'conflicts')
if os.path.isfile(conflicts_file):
f = file(conflicts_file)
names = [line.strip() for line in f.readlines()]
if p.wait():
raise GitException, '%s failed' % str(cmd)
+def _input_str(cmd, string):
+ p = popen2.Popen3(cmd, True)
+ p.tochild.write(string)
+ p.tochild.close()
+ if p.wait():
+ raise GitException, '%s failed' % str(cmd)
+
def _output(cmd):
p=popen2.Popen3(cmd, True)
output = p.fromchild.read()
# unknown files
if unknown:
- exclude_file = os.path.join(get_base_dir(), 'info', 'exclude')
+ exclude_file = os.path.join(basedir.get(), 'info', 'exclude')
base_exclude = ['--exclude=%s' % s for s in
['*.[ao]', '*.pyc', '.*', '*~', '#*', 'TAGS', 'tags']]
base_exclude.append('--exclude-per-directory=.gitignore')
if noexclude:
extra_exclude = base_exclude = []
- lines = _output_lines(['git-ls-files', '--others'] + base_exclude
- + extra_exclude)
+ lines = _output_lines(['git-ls-files', '--others', '--directory']
+ + base_exclude + extra_exclude)
cache_files += [('?', line.strip()) for line in lines]
# conflicted files
def get_head_file():
"""Returns the name of the file pointed to by the HEAD link
"""
- return os.path.basename(_output_one_line('git-symbolic-ref HEAD'))
+ return strip_prefix('refs/heads/',
+ _output_one_line('git-symbolic-ref HEAD'))
def set_head_file(ref):
"""Resets HEAD to point to a new ref
# head cache flushing is needed since we might have a different value
# in the new head
__clear_head_cache()
- if __run('git-symbolic-ref HEAD', [ref]) != 0:
+ if __run('git-symbolic-ref HEAD',
+ [os.path.join('refs', 'heads', ref)]) != 0:
raise GitException, 'Could not set head to "%s"' % ref
def __set_head(val):
def branch_exists(branch):
"""Existence check for the named branch
"""
- for line in _output_lines(['git-rev-parse', '--symbolic', '--all']):
+ branch = os.path.join('refs', 'heads', branch)
+ for line in _output_lines('git-rev-parse --symbolic --all 2>&1'):
if line.strip() == branch:
return True
+ if re.compile('[ |/]'+branch+' ').search(line):
+ raise GitException, 'Bogus branch: %s' % line
return False
def create_branch(new_branch, tree_id = None):
"""Create a new branch in the git repository
"""
- new_head = os.path.join('refs', 'heads', new_branch)
- if branch_exists(new_head):
+ if branch_exists(new_branch):
raise GitException, 'Branch "%s" already exists' % new_branch
current_head = get_head()
- set_head_file(new_head)
+ set_head_file(new_branch)
__set_head(current_head)
# a checkout isn't needed if new branch points to the current head
if tree_id:
switch(tree_id)
- if os.path.isfile(os.path.join(get_base_dir(), 'MERGE_HEAD')):
- os.remove(os.path.join(get_base_dir(), 'MERGE_HEAD'))
+ if os.path.isfile(os.path.join(basedir.get(), 'MERGE_HEAD')):
+ os.remove(os.path.join(basedir.get(), 'MERGE_HEAD'))
-def switch_branch(name):
+def switch_branch(new_branch):
"""Switch to a git branch
"""
global __head
- new_head = os.path.join('refs', 'heads', name)
- if not branch_exists(new_head):
- raise GitException, 'Branch "%s" does not exist' % name
+ if not branch_exists(new_branch):
+ raise GitException, 'Branch "%s" does not exist' % new_branch
- tree_id = rev_parse(new_head + '^{commit}')
+ tree_id = rev_parse(os.path.join('refs', 'heads', new_branch)
+ + '^{commit}')
if tree_id != get_head():
refresh_index()
if __run('git-read-tree -u -m', [get_head(), tree_id]) != 0:
raise GitException, 'git-read-tree failed (local changes maybe?)'
__head = tree_id
- set_head_file(new_head)
+ set_head_file(new_branch)
- if os.path.isfile(os.path.join(get_base_dir(), 'MERGE_HEAD')):
- os.remove(os.path.join(get_base_dir(), 'MERGE_HEAD'))
+ if os.path.isfile(os.path.join(basedir.get(), 'MERGE_HEAD')):
+ os.remove(os.path.join(basedir.get(), 'MERGE_HEAD'))
def delete_branch(name):
"""Delete a git branch
"""
- branch_head = os.path.join('refs', 'heads', name)
- if not branch_exists(branch_head):
+ if not branch_exists(name):
raise GitException, 'Branch "%s" does not exist' % name
- os.remove(os.path.join(get_base_dir(), branch_head))
+ remove_file_and_dirs(os.path.join(basedir.get(), 'refs', 'heads'),
+ name)
def rename_branch(from_name, to_name):
"""Rename a git branch
"""
- from_head = os.path.join('refs', 'heads', from_name)
- if not branch_exists(from_head):
+ if not branch_exists(from_name):
raise GitException, 'Branch "%s" does not exist' % from_name
- to_head = os.path.join('refs', 'heads', to_name)
- if branch_exists(to_head):
+ if branch_exists(to_name):
raise GitException, 'Branch "%s" already exists' % to_name
if get_head_file() == from_name:
- set_head_file(to_head)
- os.rename(os.path.join(get_base_dir(), from_head), \
- os.path.join(get_base_dir(), to_head))
+ set_head_file(to_name)
+ rename(os.path.join(basedir.get(), 'refs', 'heads'),
+ from_name, to_name)
def add(names):
"""Add the files or recursively add the directory contents
return commit_id
-def apply_diff(rev1, rev2, check_index = True):
+def apply_diff(rev1, rev2, check_index = True, files = None):
"""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,
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
+ if not files:
+ files = []
+
+ diff_str = diff(files, rev1, rev2)
+ if diff_str:
+ try:
+ _input_str('git-apply %s' % index_opt, diff_str)
+ except GitException:
+ return False
+
+ return True
def merge(base, head1, head2):
"""Perform a 3-way merge between base, head1 and head2 into the
return result.rstrip()
+def pretty_commit(commit_id = 'HEAD'):
+ """Return a given commit (log + diff)
+ """
+ return _output(['git-diff-tree', '--cc', '--always', '--pretty', '-r',
+ commit_id])
+
def checkout(files = None, tree_id = None, force = False):
"""Check out the given or all files
"""
if not files:
files = []
- if tree_id and __run('git-read-tree -m', [tree_id]) != 0:
- raise GitException, 'Failed git-read-tree -m %s' % tree_id
+ if tree_id and __run('git-read-tree --reset', [tree_id]) != 0:
+ raise GitException, 'Failed git-read-tree --reset %s' % tree_id
checkout_cmd = 'git-checkout-index -q -u'
if force:
__set_head(tree_id)
-def reset(files = None, tree_id = None):
+def reset(files = None, tree_id = None, check_out = True):
"""Revert the tree changes relative to the given tree_id. It removes
any local changes
"""
if not tree_id:
tree_id = get_head()
- checkout(files, tree_id, True)
+ if check_out:
+ cache_files = __tree_status(files, tree_id)
+ # files which were added but need to be removed
+ rm_files = [x[1] for x in cache_files if x[0] in ['A']]
+
+ 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:
def pull(repository = 'origin', refspec = None):
"""Pull changes from the remote repository. At the moment, just
- use the 'git pull' command
+ use the 'git-pull' command
"""
- # 'git pull' updates the HEAD
+ # 'git-pull' updates the HEAD
__clear_head_cache()
args = [repository]
if refspec:
args.append(refspec)
- if __run('git pull', args) != 0:
- raise GitException, 'Failed "git pull %s"' % repository
+ if __run('git-pull', args) != 0:
+ raise GitException, 'Failed "git-pull %s"' % repository
def apply_patch(filename = None, base = None):
"""Apply a patch onto the current or given index. There must not
def clone(repository, local_dir):
"""Clone a remote repository. At the moment, just use the
- 'git clone' script
+ 'git-clone' script
"""
- if __run('git clone', [repository, local_dir]) != 0:
- raise GitException, 'Failed "git clone %s %s"' \
+ if __run('git-clone', [repository, local_dir]) != 0:
+ raise GitException, 'Failed "git-clone %s %s"' \
% (repository, local_dir)
def modifying_revs(files, base_rev):