def get_conflicts():
"""Return the list of file conflicts
"""
- names = []
+ names = set()
for line in GRun('ls-files', '-z', '--unmerged'
).raw_output().split('\0')[:-1]:
stat, path = line.split('\t', 1)
- # Look for entries in stage 2 (could equally well use 3)
- if stat.endswith(' 2'):
- names.append(path)
- return names
+ names.add(path)
+ return list(names)
def exclude_files():
files = [os.path.join(basedir.get(), 'info', 'exclude')]
files.append(user_exclude)
return files
-def ls_files(files, tree = None, full_name = True):
+def ls_files(files, tree = 'HEAD', full_name = True):
"""Return the files known to GIT or raise an error otherwise. It also
converts the file to the full path relative the the .git directory.
"""
args.append('--')
args.extend(files)
try:
- return GRun('ls-files', '--error-unmatch', *args).output_lines()
+ # use a set to avoid file names duplication due to different stages
+ fileset = set(GRun('ls-files', '--error-unmatch', *args).output_lines())
except GitRunException:
# just hide the details of the 'git ls-files' command we use
raise GitException, \
'Some of the given paths are either missing or not known to GIT'
+ return list(fileset)
+
+def parse_git_ls(output):
+ """Parse the output of git diff-index, diff-files, etc. Doesn't handle
+ rename/copy output, so don't feed it output generated with the -M
+ or -C flags."""
+ t = None
+ for line in output.split('\0'):
+ if not line:
+ # There's a zero byte at the end of the output, which
+ # gives us an empty string as the last "line".
+ continue
+ if t == None:
+ mode_a, mode_b, sha1_a, sha1_b, t = line.split(' ')
+ else:
+ yield (t, line)
+ t = None
def tree_status(files = None, tree_id = 'HEAD', unknown = False,
- noexclude = True, verbose = False, diff_flags = []):
+ noexclude = True, verbose = False):
"""Get the status of all changed files, or of a selected set of
files. Returns a list of pairs - (status, filename).
refresh_index()
+ if files is None:
+ files = []
cache_files = []
# unknown files
cache_files += [('C', filename) for filename in conflicts
if not files or filename in files]
reported_files = set(conflicts)
+ files_left = [f for f in files if f not in reported_files]
- # files in the index
- args = diff_flags + [tree_id]
- if files:
- args += ['--'] + files
- for line in GRun('diff-index', *args).output_lines():
- fs = tuple(line.rstrip().split(' ',4)[-1].split('\t',1))
- if fs[1] not in reported_files:
- cache_files.append(fs)
- reported_files.add(fs[1])
-
- # files in the index but changed on (or removed from) disk
- args = list(diff_flags)
- if files:
- args += ['--'] + files
- for line in GRun('diff-files', *args).output_lines():
- fs = tuple(line.rstrip().split(' ',4)[-1].split('\t',1))
- if fs[1] not in reported_files:
- cache_files.append(fs)
- reported_files.add(fs[1])
+ # files in the index. Only execute this code if no files were
+ # specified when calling the function (i.e. report all files) or
+ # files were specified but already found in the previous step
+ if not files or files_left:
+ args = [tree_id]
+ if files_left:
+ args += ['--'] + files_left
+ for t, fn in parse_git_ls(GRun('diff-index', '-z', *args).raw_output()):
+ # the condition is needed in case files is emtpy and
+ # diff-index lists those already reported
+ if not fn in reported_files:
+ cache_files.append((t, fn))
+ reported_files.add(fn)
+ files_left = [f for f in files if f not in reported_files]
+
+ # files in the index but changed on (or removed from) disk. Only
+ # execute this code if no files were specified when calling the
+ # function (i.e. report all files) or files were specified but
+ # already found in the previous step
+ if not files or files_left:
+ args = []
+ if files_left:
+ args += ['--'] + files_left
+ for t, fn in parse_git_ls(GRun('diff-files', '-z', *args).raw_output()):
+ # the condition is needed in case files is empty and
+ # diff-files lists those already reported
+ if not fn in reported_files:
+ cache_files.append((t, fn))
+ reported_files.add(fn)
if verbose:
out.done()
and os.path.exists(os.path.join(reflog_dir, from_name)):
rename(reflog_dir, from_name, to_name)
-def add(names):
- """Add the files or recursively add the directory contents
- """
- # generate the file list
- files = []
- for i in names:
- if not os.path.exists(i):
- raise GitException, 'Unknown file or directory: %s' % i
-
- if os.path.isdir(i):
- # recursive search. We only add files
- for root, dirs, local_files in os.walk(i):
- for name in [os.path.join(root, f) for f in local_files]:
- if os.path.isfile(name):
- files.append(os.path.normpath(name))
- elif os.path.isfile(i):
- files.append(os.path.normpath(i))
- else:
- raise GitException, '%s is not a file or directory' % i
-
- if files:
- try:
- GRun('update-index', '--add', '--').xargs(files)
- except GitRunException:
- raise GitException, 'Unable to add file'
-
-def __copy_single(source, target, target2=''):
- """Copy file or dir named 'source' to name target+target2"""
-
- # "source" (file or dir) must match one or more git-controlled file
- realfiles = GRun('ls-files', source).output_lines()
- if len(realfiles) == 0:
- raise GitException, '"%s" matches no git-controled files' % source
-
- if os.path.isdir(source):
- # physically copy the files, and record them to add them in one run
- newfiles = []
- re_string='^'+source+'/(.*)$'
- prefix_regexp = re.compile(re_string)
- for f in [f.strip() for f in realfiles]:
- m = prefix_regexp.match(f)
- if not m:
- raise Exception, '"%s" does not match "%s"' % (f, re_string)
- newname = target+target2+'/'+m.group(1)
- if not os.path.exists(os.path.dirname(newname)):
- os.makedirs(os.path.dirname(newname))
- copyfile(f, newname)
- newfiles.append(newname)
-
- add(newfiles)
- else: # files, symlinks, ...
- newname = target+target2
- copyfile(source, newname)
- add([newname])
-
-
-def copy(filespecs, target):
- if os.path.isdir(target):
- # target is a directory: copy each entry on the command line,
- # with the same name, into the target
- target = target.rstrip('/')
-
- # first, check that none of the children of the target
- # matching the command line aleady exist
- for filespec in filespecs:
- entry = target+ '/' + os.path.basename(filespec.rstrip('/'))
- if os.path.exists(entry):
- raise GitException, 'Target "%s" already exists' % entry
-
- for filespec in filespecs:
- filespec = filespec.rstrip('/')
- basename = '/' + os.path.basename(filespec)
- __copy_single(filespec, target, basename)
-
- elif os.path.exists(target):
- raise GitException, 'Target "%s" exists but is not a directory' % target
- elif len(filespecs) != 1:
- raise GitException, 'Cannot copy more than one file to non-directory'
-
- else:
- # at this point: len(filespecs)==1 and target does not exist
-
- # check target directory
- targetdir = os.path.dirname(target)
- if targetdir != '' and not os.path.isdir(targetdir):
- raise GitException, 'Target directory "%s" does not exist' % targetdir
-
- __copy_single(filespecs[0].rstrip('/'), target)
-
-
-def rm(files, force = False):
- """Remove a file from the repository
- """
- if not force:
- for f in files:
- if os.path.exists(f):
- raise GitException, '%s exists. Remove it first' %f
- if files:
- GRun('update-index', '--remove', '--').xargs(files)
- else:
- if files:
- GRun('update-index', '--force-remove', '--').xargs(files)
-
# Persons caching
__user = None
__author = None
else:
return ''
-# TODO: take another parameter representing a diff string as we
-# usually invoke git.diff() form the calling functions
-def diffstat(files = None, rev1 = 'HEAD', rev2 = None):
- """Return the diffstat between rev1 and rev2."""
- return GRun('apply', '--stat', '--summary'
- ).raw_input(diff(files, rev1, rev2)).raw_output()
+def diffstat(diff):
+ """Return the diffstat of the supplied diff."""
+ return GRun('apply', '--stat', '--summary').raw_input(diff).raw_output()
def files(rev1, rev2, diff_flags = []):
"""Return the files modified between rev1 and rev2
return m.group(1)
else:
raise GitException, 'Cannot parse refspec "%s"' % line
-
def __remotes_from_config():
return config.sections_matching(r'remote\.(.*)\.url')