| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -62,6 +62,15 @@ Index | |||
| 62 | 62 | * Proper merge handling with index and working copy | |
| 63 | 63 | * Checkout individual blobs using the index and git-checkout. Blobs can already | |
| 64 | 64 | be written using their stream_data method. | |
| 65 | + * index.add: could be implemented in python together with hash-object, allowing | ||
| 66 | + to keep the internal entry cache and write once everything is done. Problem | ||
| 67 | + would be that all other git commands are unaware of the changes unless the index | ||
| 68 | + gets written. Its worth an evaluation at least. | ||
| 69 | + * index.remove: On windows, there can be a command line length overflow | ||
| 70 | + as we pass the paths directly as argv. This is as we use git-rm to be able | ||
| 71 | + to remove whole directories easily. This could be implemented using | ||
| 72 | + git-update-index if this becomes an issue, but then we had to do all the globbing | ||
| 73 | + and directory removal ourselves | ||
| 65 | 74 | ||
| 66 | 75 | Refs | |
| 67 | 76 | ----- | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -164,6 +164,26 @@ def from_blob(cls, blob): | |||
| 164 | 164 | return IndexEntry((blob.mode, blob.id, 0, blob.path, time, time, 0, 0, 0, 0, blob.size)) | |
| 165 | 165 | ||
| 166 | 166 | ||
| 167 | + def clear_cache(func): | ||
| 168 | + """ | ||
| 169 | + Decorator for functions that alter the index using the git command. This would | ||
| 170 | + invalidate our possibly existing entries dictionary which is why it must be | ||
| 171 | + deleted to allow it to be lazily reread later. | ||
| 172 | + | ||
| 173 | + Note | ||
| 174 | + This decorator will not be required once all functions are implemented | ||
| 175 | + natively which in fact is possible, but probably not feasible performance wise. | ||
| 176 | + """ | ||
| 177 | + def clear_cache_if_not_raised(self, *args, **kwargs): | ||
| 178 | + rval = func(self, *args, **kwargs) | ||
| 179 | + del(self.entries) | ||
| 180 | + return rval | ||
| 181 | + | ||
| 182 | + # END wrapper method | ||
| 183 | + clear_cache_if_not_raised.__name__ = func.__name__ | ||
| 184 | + return clear_cache_if_not_raised | ||
| 185 | + | ||
| 186 | + | ||
| 167 | 187 | def default_index(func): | |
| 168 | 188 | """ | |
| 169 | 189 | Decorator assuring the wrapped method may only run if we are the default | |
@@ -194,10 +214,8 @@ class IndexFile(LazyMixin, diff.Diffable): | |||
| 194 | 214 | The index contains an entries dict whose keys are tuples of type IndexEntry | |
| 195 | 215 | to facilitate access. | |
| 196 | 216 | ||
| 197 | - As opposed to the Index type, the IndexFile represents the index on file level. | ||
| 198 | - This can be considered an alternate, file-based implementation of the Index class | ||
| 199 | - with less support for common functions. Use it for very special and custom index | ||
| 200 | - handling. | ||
| 217 | + You may only read the entries dict or manipulate it through designated methods. | ||
| 218 | + Otherwise changes to it will be lost when changing the index using its methods. | ||
| 201 | 219 | """ | |
| 202 | 220 | __slots__ = ( "repo", "version", "entries", "_extension_data", "_file_path" ) | |
| 203 | 221 | _VERSION = 2 # latest version we support | |
@@ -500,6 +518,10 @@ def resolve_blobs(self, iter_blobs): | |||
| 500 | 518 | ||
| 501 | 519 | Returns: | |
| 502 | 520 | self | |
| 521 | + | ||
| 522 | + Note | ||
| 523 | + You will have to write the index manually once you are done, i.e. | ||
| 524 | + index.resolve_blobs(blobs).write() | ||
| 503 | 525 | """ | |
| 504 | 526 | for blob in iter_blobs: | |
| 505 | 527 | stage_null_key = (blob.path, 0) | |
@@ -561,45 +583,132 @@ def _process_diff_args(self, args): | |||
| 561 | 583 | # END remove self | |
| 562 | 584 | return args | |
| 563 | 585 | ||
| 586 | + | ||
| 587 | + def _to_relative_path(self, path): | ||
| 588 | + """ | ||
| 589 | + Return | ||
| 590 | + Version of path relative to our git directory or raise ValueError | ||
| 591 | + if it is not within our git direcotory | ||
| 592 | + """ | ||
| 593 | + if not os.path.isabs(path): | ||
| 594 | + return path | ||
| 595 | + relative_path = path.replace(self.repo.git.git_dir+"/", "") | ||
| 596 | + if relative_path == path: | ||
| 597 | + raise ValueError("Absolute path %r is not in git repository at %r" % (path,self.repo.git.git_dir)) | ||
| 598 | + return relative_path | ||
| 599 | + | ||
| 600 | + @clear_cache | ||
| 564 | 601 | @default_index | |
| 565 | 602 | def add(self, items, **kwargs): | |
| 566 | 603 | """ | |
| 567 | - Add files from the working copy, specific blobs or IndexEntries | ||
| 568 | - to the index. | ||
| 569 | - | ||
| 570 | - TODO: Its important to specify a way to add symlinks directly, even | ||
| 571 | - on systems that do not support it, like ... erm ... windows. | ||
| 604 | + Add files from the working tree, specific blobs or BaseIndexEntries | ||
| 605 | + to the index. The underlying index file will be written immediately, hence | ||
| 606 | + you should provide as many items as possible to minimize the amounts of writes | ||
| 572 | 607 | ||
| 608 | + ``items`` | ||
| 609 | + Multiple types of items are supported, types can be mixed within one call. | ||
| 610 | + Different types imply a different handling. File paths may generally be | ||
| 611 | + relative or absolute. | ||
| 612 | + | ||
| 613 | + - path string | ||
| 614 | + strings denote a relative or absolute path into the repository pointing to | ||
| 615 | + an existing file, i.e. CHANGES, lib/myfile.ext, /home/gitrepo/lib/myfile.ext. | ||
| 616 | + | ||
| 617 | + Paths provided like this must exist. When added, they will be written | ||
| 618 | + into the object database. | ||
| 619 | + | ||
| 620 | + This equals a straight git-add. | ||
| 621 | + | ||
| 622 | + They are added at stage 0 | ||
| 623 | + | ||
| 624 | + - Blob object | ||
| 625 | + Blobs are added as they are assuming a valid mode is set. | ||
| 626 | + The file they refer to may or may not exist in the file system | ||
| 627 | + | ||
| 628 | + If their sha is null ( 40*0 ), their path must exist in the file system | ||
| 629 | + as an object will be created from the data at the path.The handling | ||
| 630 | + now very much equals the way string paths are processed, except that | ||
| 631 | + the mode you have set will be kept. This allows you to create symlinks | ||
| 632 | + by settings the mode respectively and writing the target of the symlink | ||
| 633 | + directly into the file. This equals a default Linux-Symlink which | ||
| 634 | + is not dereferenced automatically, except that it can be created on | ||
| 635 | + filesystems not supporting it as well. | ||
| 636 | + | ||
| 637 | + They are added at stage 0 | ||
| 638 | + | ||
| 639 | + - BaseIndexEntry or type | ||
| 640 | + Handling equals the one of Blob objects, but the stage may be | ||
| 641 | + explicitly set. | ||
| 642 | + | ||
| 573 | 643 | ``**kwargs`` | |
| 574 | - Additional keyword arguments to be passed to git-update-index | ||
| 644 | + Additional keyword arguments to be passed to git-update-index, such | ||
| 645 | + as index_only. | ||
| 575 | 646 | ||
| 576 | 647 | Returns | |
| 577 | - List(IndexEntries) representing the entries just added | ||
| 648 | + List(BaseIndexEntries) representing the entries just actually added. | ||
| 578 | 649 | """ | |
| 579 | 650 | raise NotImplementedError("todo") | |
| 580 | 651 | ||
| 652 | + @clear_cache | ||
| 581 | 653 | @default_index | |
| 582 | - def remove(self, items, affect_working_tree=False, **kwargs): | ||
| 654 | + def remove(self, items, working_tree=False, **kwargs): | ||
| 583 | 655 | """ | |
| 584 | - Remove the given file_paths or blobs from the index and optionally from | ||
| 656 | + Remove the given items from the index and optionally from | ||
| 585 | 657 | the working tree as well. | |
| 586 | 658 | ||
| 587 | 659 | ``items`` | |
| 588 | - TODO | ||
| 660 | + Multiple types of items are supported which may be be freely mixed. | ||
| 661 | + | ||
| 662 | + - path string | ||
| 663 | + Remove the given path at all stages. If it is a directory, you must | ||
| 664 | + specify the r=True keyword argument to remove all file entries | ||
| 665 | + below it. If absolute paths are given, they will be converted | ||
| 666 | + to a path relative to the git repository directory containing | ||
| 667 | + the working tree | ||
| 668 | + | ||
| 669 | + The path string may include globs, such as *.c. | ||
| 670 | + | ||
| 671 | + - Blob object | ||
| 672 | + Only the path portion is used in this case. | ||
| 673 | + | ||
| 674 | + - BaseIndexEntry or compatible type | ||
| 675 | + The only relevant information here Yis the path. The stage is ignored. | ||
| 589 | 676 | ||
| 590 | - ``affect_working_tree`` | ||
| 677 | + ``working_tree`` | ||
| 591 | 678 | If True, the entry will also be removed from the working tree, physically | |
| 592 | 679 | removing the respective file. This may fail if there are uncommited changes | |
| 593 | 680 | in it. | |
| 594 | 681 | ||
| 595 | 682 | ``**kwargs`` | |
| 596 | - Additional keyword arguments to be passed to git-update-index | ||
| 683 | + Additional keyword arguments to be passed to git-rm, such | ||
| 684 | + as 'r' to allow recurive removal of | ||
| 597 | 685 | ||
| 598 | 686 | Returns | |
| 599 | - self | ||
| 600 | - """ | ||
| 601 | - raise NotImplementedError("todo") | ||
| 602 | - return self | ||
| 687 | + List(path_string, ...) list of paths that have been removed effectively. | ||
| 688 | + This is interesting to know in case you have provided a directory or | ||
| 689 | + globs. Paths are relative to the | ||
| 690 | + """ | ||
| 691 | + args = list() | ||
| 692 | + if not working_tree: | ||
| 693 | + args.append("--cached") | ||
| 694 | + args.append("--") | ||
| 695 | + | ||
| 696 | + # preprocess paths | ||
| 697 | + paths = list() | ||
| 698 | + for item in items: | ||
| 699 | + if isinstance(item, (BaseIndexEntry,Blob)): | ||
| 700 | + paths.append(self._to_relative_path(item.path)) | ||
| 701 | + elif isinstance(item, basestring): | ||
| 702 | + paths.append(self._to_relative_path(item)) | ||
| 703 | + else: | ||
| 704 | + raise TypeError("Invalid item type: %r" % item) | ||
| 705 | + # END for each item | ||
| 706 | + | ||
| 707 | + removed_paths = self.repo.git.rm(args, paths, **kwargs).splitlines() | ||
| 708 | + | ||
| 709 | + # process output to gain proper paths | ||
| 710 | + # rm 'path' | ||
| 711 | + return [ p[4:-1] for p in removed_paths ] | ||
| 603 | 712 | ||
| 604 | 713 | @default_index | |
| 605 | 714 | def commit(self, message=None, parent_commits=None, **kwargs): | |
@@ -621,7 +730,8 @@ def commit(self, message=None, parent_commits=None, **kwargs): | |||
| 621 | 730 | Commit object representing the new commit | |
| 622 | 731 | """ | |
| 623 | 732 | raise NotImplementedError("todo") | |
| 624 | - | ||
| 733 | + | ||
| 734 | + @clear_cache | ||
| 625 | 735 | @default_index | |
| 626 | 736 | def reset(self, commit='HEAD', working_tree=False, paths=None, **kwargs): | |
| 627 | 737 | """ | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -20,6 +20,7 @@ class Object(LazyMixin): | |||
| 20 | 20 | inst.size # objects uncompressed data size | |
| 21 | 21 | inst.data # byte string containing the whole data of the object | |
| 22 | 22 | """ | |
| 23 | + NULL_HEX_SHA = '0'*40 | ||
| 23 | 24 | TYPES = ("blob", "tree", "commit", "tag") | |
| 24 | 25 | __slots__ = ("repo", "id", "size", "data" ) | |
| 25 | 26 | type = None # to be set by subclass | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -9,6 +9,7 @@ | |||
| 9 | 9 | import inspect | |
| 10 | 10 | import os | |
| 11 | 11 | import tempfile | |
| 12 | + import glob | ||
| 12 | 13 | ||
| 13 | 14 | class TestTree(TestBase): | |
| 14 | 15 | ||
@@ -174,8 +175,67 @@ def test_index_file_diffing(self, rw_repo): | |||
| 174 | 175 | finally: | |
| 175 | 176 | fp.close() | |
| 176 | 177 | ||
| 178 | + | ||
| 179 | + def _count_existing(self, repo, files): | ||
| 180 | + existing = 0 | ||
| 181 | + basedir = repo.git.git_dir | ||
| 182 | + for f in files: | ||
| 183 | + existing += os.path.isfile(os.path.join(basedir, f)) | ||
| 184 | + # END for each deleted file | ||
| 185 | + return existing | ||
| 186 | + # END num existing helper | ||
| 187 | + | ||
| 188 | + | ||
| 189 | + | ||
| 177 | 190 | @with_rw_repo('0.1.6') | |
| 178 | 191 | def test_index_mutation(self, rw_repo): | |
| 179 | - # add / remove / commit / Working Tree Handling | ||
| 180 | - self.fail( "add, remove, commit, working tree handling" ) | ||
| 192 | + index = rw_repo.index | ||
| 193 | + num_entries = len(index.entries) | ||
| 194 | + # remove all of the files, provide a wild mix of paths, BaseIndexEntries, | ||
| 195 | + # IndexEntries | ||
| 196 | + def mixed_iterator(): | ||
| 197 | + count = 0 | ||
| 198 | + for entry in index.entries.itervalues(): | ||
| 199 | + type_id = count % 4 | ||
| 200 | + if type_id == 0: # path | ||
| 201 | + yield entry.path | ||
| 202 | + elif type_id == 1: # blob | ||
| 203 | + yield Blob(rw_repo, entry.sha, entry.mode, entry.path) | ||
| 204 | + elif type_id == 2: # BaseIndexEntry | ||
| 205 | + yield BaseIndexEntry(entry[:4]) | ||
| 206 | + elif type_id == 3: # IndexEntry | ||
| 207 | + yield entry | ||
| 208 | + else: | ||
| 209 | + raise AssertionError("Invalid Type") | ||
| 210 | + count += 1 | ||
| 211 | + # END for each entry | ||
| 212 | + # END mixed iterator | ||
| 213 | + deleted_files = index.remove(mixed_iterator(), working_tree=False) | ||
| 214 | + assert deleted_files | ||
| 215 | + assert self._count_existing(rw_repo, deleted_files) == len(deleted_files) | ||
| 216 | + assert len(index.entries) == 0 | ||
| 217 | + | ||
| 218 | + # reset the index to undo our changes | ||
| 219 | + index.reset() | ||
| 220 | + assert len(index.entries) == num_entries | ||
| 221 | + | ||
| 222 | + # remove with working copy | ||
| 223 | + deleted_files = index.remove(mixed_iterator(), working_tree=True) | ||
| 224 | + assert deleted_files | ||
| 225 | + assert self._count_existing(rw_repo, deleted_files) == 0 | ||
| 226 | + | ||
| 227 | + # reset everything | ||
| 228 | + index.reset(working_tree=True) | ||
| 229 | + assert self._count_existing(rw_repo, deleted_files) == len(deleted_files) | ||
| 230 | + | ||
| 231 | + # invalid type | ||
| 232 | + self.failUnlessRaises(TypeError, index.remove, [1]) | ||
| 233 | + | ||
| 234 | + # absolute path | ||
| 235 | + deleted_files = index.remove([os.path.join(rw_repo.git.git_dir,"lib")], r=True) | ||
| 236 | + assert len(deleted_files) > 1 | ||
| 237 | + self.failUnlessRaises(ValueError, index.remove, ["/doesnt/exists"]) | ||
| 238 | + | ||
| 239 | + # re-add all files in lib | ||
| 240 | + self.fail( "add, commit, working tree handling" ) | ||
| 181 | 241 | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments