Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 84f9b60

Browse files
committed
index.add: added progress function support and made sure progress is sent just in time
This adds a dependency to a git update index fix - and I hope it will be put in in time ( including windows version )
1 parent 84be126 commit 84f9b60

File tree

3 files changed

+168
-28
lines changed

3 files changed

+168
-28
lines changed

doc/intro.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ Requirements
1515
============
1616

1717
* Git_ tested with 1.5.3.7
18+
* Requires Git_ 1.6.5.4 or newer if index.add function is to be used
1819
* `Python Nose`_ - used for running the tests
1920
* `Mock by Michael Foord`_ used for tests. Requires 0.5
2021

lib/git/index.py

Lines changed: 121 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@
1616
import sys
1717
import stat
1818
import subprocess
19+
import glob
1920
import git.diff as diff
2021

22+
from errors import GitCommandError
2123
from git.objects import Blob, Tree, Object, Commit
2224
from git.utils import SHA1Writer, LazyMixin, ConcurrentWriteOperation, join_path_native
2325

@@ -662,7 +664,7 @@ def _preprocess_add_items(self, items):
662664

663665
@clear_cache
664666
@default_index
665-
def add(self, items, force=True, **kwargs):
667+
def add(self, items, force=True, fprogress=lambda *args: None):
666668
"""
667669
Add files from the working tree, specific blobs or BaseIndexEntries
668670
to the index. The underlying index file will be written immediately, hence
@@ -717,43 +719,130 @@ def add(self, items, force=True, **kwargs):
717719
as the API user usually wants the item to be added even though
718720
they might be excluded.
719721
720-
``**kwargs``
721-
Additional keyword arguments to be passed to git-update-index, such
722-
as index_only.
723-
722+
``fprogress``
723+
Function with signature f(path, done=False, item=item) called for each
724+
path to be added, once once it is about to be added where done==False
725+
and once after it was added where done=True.
726+
item is set to the actual item we handle, either a Path or a BaseIndexEntry
727+
Please note that the processed path is not guaranteed to be present
728+
in the index already as the index is currently being processed.
729+
724730
Returns
725731
List(BaseIndexEntries) representing the entries just actually added.
726-
"""
732+
733+
Raises
734+
GitCommandError if a supplied Path did not exist. Please note that BaseIndexEntry
735+
Objects that do not have a null sha will be added even if their paths
736+
do not exist.
737+
"""
738+
# UTILITIES
739+
def raise_exc(e):
740+
raise e
741+
742+
def expand_paths(paths):
743+
"""Expand the directories in list of paths to the corresponding paths accordingly,
744+
they will always be relative to the repository"""
745+
out = list()
746+
r = self.repo.git.git_dir
747+
rs = r + '/'
748+
for path in paths:
749+
abs_path = path
750+
if not os.path.isabs(abs_path):
751+
abs_path = os.path.join(r, path)
752+
# END make absolute path
753+
754+
# resolve globs if possible
755+
if '?' in path or '*' in path or '[' in path:
756+
out.extend(f.replace(rs, '') for f in expand_paths(glob.glob(abs_path)))
757+
continue
758+
# END glob handling
759+
try:
760+
for root, dirs, files in os.walk(abs_path, onerror=raise_exc):
761+
for rela_file in files:
762+
# add relative paths only
763+
out.append(os.path.join(root.replace(rs, ''), rela_file))
764+
# END for each file in subdir
765+
# END for each subdirectory
766+
except OSError:
767+
# was a file or something that could not be iterated
768+
out.append(path)
769+
# END path exception handling
770+
# END for each path
771+
772+
# NOTE: git will add items multiple times even if a glob overlapped
773+
# with manually specified paths or if paths where specified multiple
774+
# times - we respect that and do not prune
775+
return out
776+
# END expand helper method
777+
778+
def write_path_to_stdin(proc, filepath, item, fmakeexc):
779+
"""Write path to proc.stdin and make sure it processes the item, including progress.
780+
@return: stdout string"""
781+
fprogress(filepath, False, item)
782+
try:
783+
proc.stdin.write("%s\n" % filepath)
784+
except IOError:
785+
# pipe broke, usually because some error happend
786+
raise fmakeexc()
787+
# END write exception handling
788+
proc.stdin.flush()
789+
# NOTE: if this hangs, you need at lest git 1.6.5.4 as a git-bugfix
790+
# is needed for this
791+
# TODO: Rewrite this using hash-object and update index to get
792+
# rid of the bug-fix dependency, updaet intro.rst requirements
793+
rval = proc.stdout.readline().strip() # trigger operation
794+
fprogress(filepath, True, item)
795+
return rval
796+
# END write_path_to_stdin
797+
798+
727799
# sort the entries into strings and Entries, Blobs are converted to entries
728800
# automatically
729801
# paths can be git-added, for everything else we use git-update-index
730802
entries_added = list()
731803
paths, entries = self._preprocess_add_items(items)
732804

805+
# HANDLE PATHS
733806
if paths:
734-
git_add_output = self.repo.git.add(paths, v=True)
735-
# force rereading our entries
807+
# to get suitable progress information, pipe paths to stdin
808+
args = ("--add", "--replace", "--verbose", "--stdin")
809+
proc = self.repo.git.update_index(*args, **{'as_process':True, 'istream':subprocess.PIPE})
810+
make_exc = lambda : GitCommandError(("git-update-index",)+args, 128, proc.stderr.readline())
811+
filepaths=expand_paths(paths)
812+
added_files = list()
813+
814+
for filepath in filepaths:
815+
write_path_to_stdin(proc, filepath, filepath, make_exc)
816+
added_files.append(filepath)
817+
# END for each filepath
818+
self._flush_stdin_and_wait(proc) # ignore stdout
819+
820+
# force rereading our entries once it is all done
736821
del(self.entries)
737-
for line in git_add_output.splitlines():
738-
# line contains:
739-
# add '<path>'
740-
added_file = line[5:-1]
741-
entries_added.append(self.entries[(added_file,0)])
742-
# END for each line
822+
entries_added.extend(self.entries[(f,0)] for f in added_files)
743823
# END path handling
744824

825+
# HANDLE ENTRIES
745826
if entries:
746827
null_mode_entries = [ e for e in entries if e.mode == 0 ]
747828
if null_mode_entries:
748829
raise ValueError("At least one Entry has a null-mode - please use index.remove to remove files for clarity")
749830
# END null mode should be remove
750831

832+
# HANLDE ENTRY OBJECT CREATION
751833
# create objects if required, otherwise go with the existing shas
752834
null_entries_indices = [ i for i,e in enumerate(entries) if e.sha == Object.NULL_HEX_SHA ]
753835
if null_entries_indices:
754-
hash_proc = self.repo.git.hash_object(w=True, stdin_paths=True, istream=subprocess.PIPE, as_process=True)
755-
hash_proc.stdin.write('\n'.join(entries[i].path for i in null_entries_indices))
756-
obj_ids = self._flush_stdin_and_wait(hash_proc).splitlines()
836+
# creating object ids is the time consuming part. Hence we will
837+
# send progress for these now.
838+
args = ("-w", "--stdin-paths")
839+
proc = self.repo.git.hash_object(*args, **{'istream':subprocess.PIPE, 'as_process':True})
840+
make_exc = lambda : GitCommandError(("git-hash-object",)+args, 128, proc.stderr.readline())
841+
obj_ids = list()
842+
for ei in null_entries_indices:
843+
entry = entries[ei]
844+
obj_ids.append(write_path_to_stdin(proc, entry.path, entry, make_exc))
845+
# END for each entry index
757846
assert len(obj_ids) == len(null_entries_indices), "git-hash-object did not produce all requested objects: want %i, got %i" % ( len(null_entries_indices), len(obj_ids) )
758847

759848
# update IndexEntries with new object id
@@ -764,11 +853,22 @@ def add(self, items, force=True, **kwargs):
764853
# END for each index
765854
# END null_entry handling
766855

767-
# feed all the data to stdin
768-
update_index_proc = self.repo.git.update_index(index_info=True, istream=subprocess.PIPE, as_process=True, **kwargs)
769-
update_index_proc.stdin.write('\n'.join(str(e) for e in entries))
856+
# feed pure entries to stdin
857+
proc = self.repo.git.update_index(index_info=True, istream=subprocess.PIPE, as_process=True)
858+
for i, entry in enumerate(entries):
859+
progress_sent = i in null_entries_indices
860+
if not progress_sent:
861+
fprogress(entry.path, False, entry)
862+
# it cannot handle too-many newlines in this mode
863+
if i != 0:
864+
proc.stdin.write('\n')
865+
proc.stdin.write(str(entry))
866+
proc.stdin.flush()
867+
if not progress_sent:
868+
fprogress(entry.path, True, entry)
869+
# END for each enty
870+
self._flush_stdin_and_wait(proc)
770871
entries_added.extend(entries)
771-
self._flush_stdin_and_wait(update_index_proc)
772872
# END if there are base entries
773873

774874
return entries_added

test/git/test_index.py

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,35 @@
1313
import glob
1414
from stat import *
1515

16+
1617
class TestTree(TestBase):
1718

19+
def __init__(self, *args):
20+
super(TestTree, self).__init__(*args)
21+
self._reset_progress()
22+
23+
def _assert_add_progress(self, entries):
24+
assert len(entries) == len(self._add_progress_map)
25+
for path, call_count in self._add_progress_map.iteritems():
26+
assert call_count == 2
27+
self._reset_progress()
28+
29+
def _add_progress(self, path, done, item):
30+
"""Called as progress func - we keep track of the proper
31+
call order"""
32+
assert item is not None
33+
self._add_progress_map.setdefault(path, 0)
34+
curval = self._add_progress_map[path]
35+
if curval == 0:
36+
assert not done
37+
if curval == 1:
38+
assert done
39+
self._add_progress_map[path] = curval + 1
40+
41+
def _reset_progress(self):
42+
# maps paths to the count of calls
43+
self._add_progress_map = dict()
44+
1845
def test_index_file_base(self):
1946
# read from file
2047
index = IndexFile(self.rorepo, fixture_path("index"))
@@ -297,19 +324,29 @@ def mixed_iterator():
297324
assert os.path.isfile(os.path.join(rw_repo.git.git_dir, lib_file_path))
298325

299326
# directory
300-
entries = index.add(['lib'])
327+
entries = index.add(['lib'], fprogress=self._add_progress)
328+
self._assert_add_progress(entries)
301329
assert len(entries)>1
302330

303331
# glob
304-
entries = index.reset(new_commit).add(['lib/*.py'])
332+
entries = index.reset(new_commit).add(['lib/git/*.py'], fprogress=self._add_progress)
333+
self._assert_add_progress(entries)
305334
assert len(entries) == 14
306335

336+
# same file
337+
entries = index.reset(new_commit).add(['lib/git/head.py']*2, fprogress=self._add_progress)
338+
# would fail, test is too primitive to handle this case
339+
# self._assert_add_progress(entries)
340+
self._reset_progress()
341+
assert len(entries) == 2
342+
307343
# missing path
308344
self.failUnlessRaises(GitCommandError, index.reset(new_commit).add, ['doesnt/exist/must/raise'])
309345

310346
# blob from older revision overrides current index revision
311347
old_blob = new_commit.parents[0].tree.blobs[0]
312-
entries = index.reset(new_commit).add([old_blob])
348+
entries = index.reset(new_commit).add([old_blob], fprogress=self._add_progress)
349+
self._assert_add_progress(entries)
313350
assert index.entries[(old_blob.path,0)].sha == old_blob.sha and len(entries) == 1
314351

315352
# mode 0 not allowed
@@ -319,14 +356,16 @@ def mixed_iterator():
319356
# add new file
320357
new_file_relapath = "my_new_file"
321358
new_file_path = self._make_file(new_file_relapath, "hello world", rw_repo)
322-
entries = index.reset(new_commit).add([BaseIndexEntry((010644, null_sha, 0, new_file_relapath))])
359+
entries = index.reset(new_commit).add([BaseIndexEntry((010644, null_sha, 0, new_file_relapath))], fprogress=self._add_progress)
360+
self._assert_add_progress(entries)
323361
assert len(entries) == 1 and entries[0].sha != null_sha
324362

325363
# add symlink
326364
if sys.platform != "win32":
327365
link_file = os.path.join(rw_repo.git.git_dir, "my_real_symlink")
328366
os.symlink("/etc/that", link_file)
329-
entries = index.reset(new_commit).add([link_file])
367+
entries = index.reset(new_commit).add([link_file], fprogress=self._add_progress)
368+
self._assert_add_progress(entries)
330369
assert len(entries) == 1 and S_ISLNK(entries[0].mode)
331370
print "%o" % entries[0].mode
332371
# END real symlink test
@@ -336,7 +375,8 @@ def mixed_iterator():
336375
link_target = "/etc/that"
337376
fake_symlink_path = self._make_file(fake_symlink_relapath, link_target, rw_repo)
338377
fake_entry = BaseIndexEntry((0120000, null_sha, 0, fake_symlink_relapath))
339-
entries = index.reset(new_commit).add([fake_entry])
378+
entries = index.reset(new_commit).add([fake_entry], fprogress=self._add_progress)
379+
self._assert_add_progress(entries)
340380
assert entries[0].sha != null_sha
341381
assert len(entries) == 1 and S_ISLNK(entries[0].mode)
342382

@@ -363,4 +403,3 @@ def mixed_iterator():
363403
open(fake_symlink_path,'rb').read() == link_target
364404
else:
365405
assert S_ISLNK(os.lstat(fake_symlink_path)[ST_MODE])
366-

0 commit comments

Comments
 (0)