Skip to content

Commit 1dd7d64

Browse files
committed
index.checkout: improved handling of errors as the thrown exception contains exact information about the successfully checked out files as well as the failed ones
Return value of the method is an iterator to all checked out files
1 parent 5020eb8 commit 1dd7d64

File tree

2 files changed

+75
-24
lines changed

2 files changed

+75
-24
lines changed

lib/git/index.py

Lines changed: 61 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,18 @@ class CheckoutError( Exception ):
2929
changes.
3030
3131
The .failed_files attribute contains a list of relative paths that failed
32-
to be checked out as they contained changes that did not exist in the index"""
33-
def __init__(self, message, failed_files):
34-
super(CheckoutError, self).__init__(message)
32+
to be checked out as they contained changes that did not exist in the index.
33+
34+
The .valid_files attribute contains a list of relative paths to files that
35+
were checked out successfully and hence match the version stored in the
36+
index"""
37+
def __init__(self, message, failed_files, valid_files):
38+
Exception.__init__(self, message)
3539
self.failed_files = failed_files
40+
self.valid_files = valid_files
3641

3742
def __str__(self):
38-
return super(CheckoutError, self).__str__() + ":%s" % self.failed_files
43+
return Exception.__str__(self) + ":%s" % self.failed_files
3944

4045

4146
class _TemporaryFileSwap(object):
@@ -1009,29 +1014,34 @@ def _flush_stdin_and_wait(cls, proc):
10091014
@default_index
10101015
def checkout(self, paths=None, force=False, fprogress=lambda *args: None, **kwargs):
10111016
"""
1012-
Checkout the given paths or all files from the version in the index.
1017+
Checkout the given paths or all files from the version known to the index into
1018+
the working tree.
10131019
10141020
``paths``
10151021
If None, all paths in the index will be checked out. Otherwise an iterable
1016-
of relative or absolute paths or a single path pointing to files in the index
1017-
is expected.
1018-
The command will raise of files do not exist in the index ( as opposed to the
1019-
original git command who ignores them )
1020-
The provided progress information will contain None as path and item if no
1021-
explicit paths are given.
1022+
of relative or absolute paths or a single path pointing to files or directories
1023+
in the index is expected.
1024+
The command will raise of files or directories do not exist in the index
1025+
( as opposed to the original git command who ignores them ). Additionally
1026+
this command allows to checkout directories which is an extension to git-update-index.
1027+
10221028
10231029
``force``
10241030
If True, existing files will be overwritten even if they contain local modifications.
10251031
If False, these will trigger a CheckoutError.
10261032
10271033
``fprogress``
1028-
see Index.add_ for signature and explanation
1034+
see Index.add_ for signature and explanation.
1035+
The provided progress information will contain None as path and item if no
1036+
explicit paths are given. Otherwise progress information will be send
1037+
prior and after a file has been checked out
10291038
10301039
``**kwargs``
10311040
Additional arguments to be pasesd to git-checkout-index
10321041
10331042
Returns
1034-
self
1043+
iterable yielding paths to files which have been checked out and are
1044+
guaranteed to match the version stored in the index
10351045
10361046
Raise CheckoutError
10371047
If at least one file failed to be checked out. This is a summary,
@@ -1043,7 +1053,7 @@ def checkout(self, paths=None, force=False, fprogress=lambda *args: None, **kwar
10431053
if force:
10441054
args.append("--force")
10451055

1046-
def handle_stderr(proc):
1056+
def handle_stderr(proc, iter_checked_out_files):
10471057
stderr = proc.stderr.read()
10481058
if not stderr:
10491059
return
@@ -1075,32 +1085,63 @@ def handle_stderr(proc):
10751085
if unknown_lines:
10761086
raise GitCommandError(("git-checkout-index", ), 128, stderr)
10771087
if failed_files:
1078-
raise CheckoutError("Some files could not be checked out from the index due to local modifications", failed_files)
1088+
valid_files = list(set(iter_checked_out_files) - set(failed_files))
1089+
raise CheckoutError("Some files could not be checked out from the index due to local modifications", failed_files, valid_files)
10791090
# END stderr handler
10801091

1092+
10811093
if paths is None:
10821094
args.append("--all")
10831095
kwargs['as_process'] = 1
10841096
fprogress(None, False, None)
10851097
proc = self.repo.git.checkout_index(*args, **kwargs)
10861098
proc.wait()
10871099
fprogress(None, True, None)
1088-
handle_stderr(proc)
1100+
rval_iter = ( e.path for e in self.entries.itervalues() )
1101+
handle_stderr(proc, rval_iter)
1102+
return rval_iter
10891103
else:
10901104
if isinstance(paths, basestring):
10911105
paths = [paths]
10921106

10931107
args.append("--stdin")
1094-
proc = self.repo.git.checkout_index(args, as_process=True, istream=subprocess.PIPE, **kwargs)
1108+
kwargs['as_process'] = True
1109+
kwargs['istream'] = subprocess.PIPE
1110+
proc = self.repo.git.checkout_index(args, **kwargs)
10951111
make_exc = lambda : GitCommandError(("git-checkout-index",)+args, 128, proc.stderr.read())
1112+
checked_out_files = list()
10961113
for path in paths:
10971114
path = self._to_relative_path(path)
1098-
self._write_path_to_stdin(proc, path, path, make_exc, fprogress, read_from_stdout=False)
1115+
# if the item is not in the index, it could be a directory
1116+
path_is_directory = False
1117+
try:
1118+
self.entries[(path, 0)]
1119+
except KeyError:
1120+
dir = path
1121+
if not dir.endswith('/'):
1122+
dir += '/'
1123+
for entry in self.entries.itervalues():
1124+
if entry.path.startswith(dir):
1125+
p = entry.path
1126+
self._write_path_to_stdin(proc, p, p, make_exc, fprogress, read_from_stdout=False)
1127+
checked_out_files.append(p)
1128+
path_is_directory = True
1129+
# END if entry is in directory
1130+
# END for each entry
1131+
# END path exception handlnig
1132+
1133+
if not path_is_directory:
1134+
self._write_path_to_stdin(proc, path, path, make_exc, fprogress, read_from_stdout=False)
1135+
checked_out_files.append(path)
1136+
# END path is a file
10991137
# END for each path
11001138
self._flush_stdin_and_wait(proc)
1101-
handle_stderr(proc)
1139+
1140+
handle_stderr(proc, checked_out_files)
1141+
return checked_out_files
1142+
# END directory handling
11021143
# END paths handling
1103-
return self
1144+
assert "Should not reach this point"
11041145

11051146
@clear_cache
11061147
@default_index

test/git/test_index.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import sys
1212
import tempfile
1313
import glob
14+
import shutil
1415
from stat import *
1516

1617

@@ -209,19 +210,22 @@ def test_index_file_diffing(self, rw_repo):
209210

210211
# test full checkout
211212
test_file = os.path.join(rw_repo.git.git_dir, "CHANGES")
212-
os.remove(test_file)
213-
index.checkout(None, force=True, fprogress=self._fprogress)
213+
open(test_file, 'ab').write("some data")
214+
rval = index.checkout(None, force=True, fprogress=self._fprogress)
215+
assert 'CHANGES' in list(rval)
214216
self._assert_fprogress([None])
215217
assert os.path.isfile(test_file)
216218

217219
os.remove(test_file)
218-
index.checkout(None, force=False, fprogress=self._fprogress)
220+
rval = index.checkout(None, force=False, fprogress=self._fprogress)
221+
assert 'CHANGES' in list(rval)
219222
self._assert_fprogress([None])
220223
assert os.path.isfile(test_file)
221224

222225
# individual file
223226
os.remove(test_file)
224-
index.checkout(test_file, fprogress=self._fprogress)
227+
rval = index.checkout(test_file, fprogress=self._fprogress)
228+
assert list(rval)[0] == 'CHANGES'
225229
self._assert_fprogress([test_file])
226230
assert os.path.exists(test_file)
227231

@@ -238,13 +242,19 @@ def test_index_file_diffing(self, rw_repo):
238242
index.checkout(test_file)
239243
except CheckoutError, e:
240244
assert len(e.failed_files) == 1 and e.failed_files[0] == os.path.basename(test_file)
245+
assert len(e.valid_files) == 0
241246
assert open(test_file).read().endswith(append_data)
242247
else:
243248
raise AssertionError("Exception CheckoutError not thrown")
244249

245250
# if we force it it should work
246251
index.checkout(test_file, force=True)
247252
assert not open(test_file).read().endswith(append_data)
253+
254+
# checkout directory
255+
shutil.rmtree(os.path.join(rw_repo.git.git_dir, "lib"))
256+
rval = index.checkout('lib')
257+
assert len(list(rval)) > 1
248258

249259
def _count_existing(self, repo, files):
250260
"""

0 commit comments

Comments
 (0)