|
1 |
| -import base |
2 |
| -from util import Traversable |
| 1 | +import git.objects.base |
| 2 | +from util import * |
| 3 | +from git.objects.util import Traversable |
3 | 4 | from StringIO import StringIO # need a dict to set bloody .name field
|
4 | 5 | from git.util import Iterable, join_path_native, to_native_path_linux
|
5 |
| -from git.config import GitConfigParser, SectionConstraint |
| 6 | +from git.config import SectionConstraint |
6 | 7 | from git.exc import InvalidGitRepositoryError, NoSuchPathError
|
7 | 8 | import stat
|
8 | 9 | import git
|
9 | 10 |
|
10 | 11 | import os
|
11 | 12 | import sys
|
12 |
| -import weakref |
| 13 | + |
13 | 14 | import shutil
|
14 | 15 |
|
15 | 16 | __all__ = ("Submodule", "RootModule")
|
16 | 17 |
|
17 |
| -#{ Utilities |
18 |
| - |
19 |
| -def sm_section(name): |
20 |
| - """:return: section title used in .gitmodules configuration file""" |
21 |
| - return 'submodule "%s"' % name |
22 |
| - |
23 |
| -def sm_name(section): |
24 |
| - """:return: name of the submodule as parsed from the section name""" |
25 |
| - section = section.strip() |
26 |
| - return section[11:-1] |
27 |
| - |
28 |
| -def mkhead(repo, path): |
29 |
| - """:return: New branch/head instance""" |
30 |
| - return git.Head(repo, git.Head.to_full_path(path)) |
31 |
| - |
32 |
| -def unbare_repo(func): |
33 |
| - """Methods with this decorator raise InvalidGitRepositoryError if they |
34 |
| - encounter a bare repository""" |
35 |
| - def wrapper(self, *args, **kwargs): |
36 |
| - if self.repo.bare: |
37 |
| - raise InvalidGitRepositoryError("Method '%s' cannot operate on bare repositories" % func.__name__) |
38 |
| - #END bare method |
39 |
| - return func(self, *args, **kwargs) |
40 |
| - # END wrapper |
41 |
| - wrapper.__name__ = func.__name__ |
42 |
| - return wrapper |
43 |
| - |
44 |
| -def find_first_remote_branch(remotes, branch): |
45 |
| - """Find the remote branch matching the name of the given branch or raise InvalidGitRepositoryError""" |
46 |
| - for remote in remotes: |
47 |
| - try: |
48 |
| - return remote.refs[branch.name] |
49 |
| - except IndexError: |
50 |
| - continue |
51 |
| - # END exception handling |
52 |
| - #END for remote |
53 |
| - raise InvalidGitRepositoryError("Didn't find remote branch %r in any of the given remotes", branch) |
54 |
| - |
55 |
| -#} END utilities |
56 |
| - |
57 |
| - |
58 |
| -#{ Classes |
59 |
| - |
60 |
| -class SubmoduleConfigParser(GitConfigParser): |
61 |
| - """ |
62 |
| - Catches calls to _write, and updates the .gitmodules blob in the index |
63 |
| - with the new data, if we have written into a stream. Otherwise it will |
64 |
| - add the local file to the index to make it correspond with the working tree. |
65 |
| - Additionally, the cache must be cleared |
66 |
| - |
67 |
| - Please note that no mutating method will work in bare mode |
68 |
| - """ |
69 |
| - |
70 |
| - def __init__(self, *args, **kwargs): |
71 |
| - self._smref = None |
72 |
| - self._index = None |
73 |
| - self._auto_write = True |
74 |
| - super(SubmoduleConfigParser, self).__init__(*args, **kwargs) |
75 |
| - |
76 |
| - #{ Interface |
77 |
| - def set_submodule(self, submodule): |
78 |
| - """Set this instance's submodule. It must be called before |
79 |
| - the first write operation begins""" |
80 |
| - self._smref = weakref.ref(submodule) |
81 |
| - |
82 |
| - def flush_to_index(self): |
83 |
| - """Flush changes in our configuration file to the index""" |
84 |
| - assert self._smref is not None |
85 |
| - # should always have a file here |
86 |
| - assert not isinstance(self._file_or_files, StringIO) |
87 |
| - |
88 |
| - sm = self._smref() |
89 |
| - if sm is not None: |
90 |
| - index = self._index |
91 |
| - if index is None: |
92 |
| - index = sm.repo.index |
93 |
| - # END handle index |
94 |
| - index.add([sm.k_modules_file], write=self._auto_write) |
95 |
| - sm._clear_cache() |
96 |
| - # END handle weakref |
97 |
| - |
98 |
| - #} END interface |
99 |
| - |
100 |
| - #{ Overridden Methods |
101 |
| - def write(self): |
102 |
| - rval = super(SubmoduleConfigParser, self).write() |
103 |
| - self.flush_to_index() |
104 |
| - return rval |
105 |
| - # END overridden methods |
106 | 18 |
|
107 | 19 |
|
108 |
| -class Submodule(base.IndexObject, Iterable, Traversable): |
| 20 | +class Submodule(git.objects.base.IndexObject, Iterable, Traversable): |
109 | 21 | """Implements access to a git submodule. They are special in that their sha
|
110 | 22 | represents a commit in the submodule's repository which is to be checked out
|
111 | 23 | at the path of this instance.
|
@@ -879,255 +791,4 @@ def iter_items(cls, repo, parent_commit='HEAD'):
|
879 | 791 | # END for each section
|
880 | 792 |
|
881 | 793 | #} END iterable interface
|
882 |
| - |
883 |
| - |
884 |
| -class RootModule(Submodule): |
885 |
| - """A (virtual) Root of all submodules in the given repository. It can be used |
886 |
| - to more easily traverse all submodules of the master repository""" |
887 |
| - |
888 |
| - __slots__ = tuple() |
889 |
| - |
890 |
| - k_root_name = '__ROOT__' |
891 |
| - |
892 |
| - def __init__(self, repo): |
893 |
| - # repo, binsha, mode=None, path=None, name = None, parent_commit=None, url=None, ref=None) |
894 |
| - super(RootModule, self).__init__( |
895 |
| - repo, |
896 |
| - binsha = self.NULL_BIN_SHA, |
897 |
| - mode = self.k_default_mode, |
898 |
| - path = '', |
899 |
| - name = self.k_root_name, |
900 |
| - parent_commit = repo.head.commit, |
901 |
| - url = '', |
902 |
| - branch = mkhead(repo, self.k_head_default) |
903 |
| - ) |
904 |
| - |
905 |
| - |
906 |
| - def _clear_cache(self): |
907 |
| - """May not do anything""" |
908 |
| - pass |
909 |
| - |
910 |
| - #{ Interface |
911 |
| - |
912 |
| - def update(self, previous_commit=None, recursive=True, force_remove=False, init=True, to_latest_revision=False): |
913 |
| - """Update the submodules of this repository to the current HEAD commit. |
914 |
| - This method behaves smartly by determining changes of the path of a submodules |
915 |
| - repository, next to changes to the to-be-checked-out commit or the branch to be |
916 |
| - checked out. This works if the submodules ID does not change. |
917 |
| - Additionally it will detect addition and removal of submodules, which will be handled |
918 |
| - gracefully. |
919 |
| - |
920 |
| - :param previous_commit: If set to a commit'ish, the commit we should use |
921 |
| - as the previous commit the HEAD pointed to before it was set to the commit it points to now. |
922 |
| - If None, it defaults to ORIG_HEAD otherwise, or the parent of the current |
923 |
| - commit if it is not given |
924 |
| - :param recursive: if True, the children of submodules will be updated as well |
925 |
| - using the same technique |
926 |
| - :param force_remove: If submodules have been deleted, they will be forcibly removed. |
927 |
| - Otherwise the update may fail if a submodule's repository cannot be deleted as |
928 |
| - changes have been made to it (see Submodule.update() for more information) |
929 |
| - :param init: If we encounter a new module which would need to be initialized, then do it. |
930 |
| - :param to_latest_revision: If True, instead of checking out the revision pointed to |
931 |
| - by this submodule's sha, the checked out tracking branch will be merged with the |
932 |
| - newest remote branch fetched from the repository's origin""" |
933 |
| - if self.repo.bare: |
934 |
| - raise InvalidGitRepositoryError("Cannot update submodules in bare repositories") |
935 |
| - # END handle bare |
936 |
| - |
937 |
| - repo = self.repo |
938 |
| - |
939 |
| - # HANDLE COMMITS |
940 |
| - ################## |
941 |
| - cur_commit = repo.head.commit |
942 |
| - if previous_commit is None: |
943 |
| - symref = repo.head.orig_head() |
944 |
| - try: |
945 |
| - previous_commit = symref.commit |
946 |
| - except Exception: |
947 |
| - pcommits = cur_commit.parents |
948 |
| - if pcommits: |
949 |
| - previous_commit = pcommits[0] |
950 |
| - else: |
951 |
| - # in this special case, we just diff against ourselve, which |
952 |
| - # means exactly no change |
953 |
| - previous_commit = cur_commit |
954 |
| - # END handle initial commit |
955 |
| - # END no ORIG_HEAD |
956 |
| - else: |
957 |
| - previous_commit = repo.commit(previous_commit) # obtain commit object |
958 |
| - # END handle previous commit |
959 |
| - |
960 |
| - |
961 |
| - psms = self.list_items(repo, parent_commit=previous_commit) |
962 |
| - sms = self.list_items(self.module()) |
963 |
| - spsms = set(psms) |
964 |
| - ssms = set(sms) |
965 |
| - |
966 |
| - # HANDLE REMOVALS |
967 |
| - ################### |
968 |
| - for rsm in (spsms - ssms): |
969 |
| - # fake it into thinking its at the current commit to allow deletion |
970 |
| - # of previous module. Trigger the cache to be updated before that |
971 |
| - #rsm.url |
972 |
| - rsm._parent_commit = repo.head.commit |
973 |
| - rsm.remove(configuration=False, module=True, force=force_remove) |
974 |
| - # END for each removed submodule |
975 |
| - |
976 |
| - # HANDLE PATH RENAMES |
977 |
| - ##################### |
978 |
| - # url changes + branch changes |
979 |
| - for csm in (spsms & ssms): |
980 |
| - psm = psms[csm.name] |
981 |
| - sm = sms[csm.name] |
982 |
| - |
983 |
| - if sm.path != psm.path and psm.module_exists(): |
984 |
| - # move the module to the new path |
985 |
| - psm.move(sm.path, module=True, configuration=False) |
986 |
| - # END handle path changes |
987 |
| - |
988 |
| - if sm.module_exists(): |
989 |
| - # handle url change |
990 |
| - if sm.url != psm.url: |
991 |
| - # Add the new remote, remove the old one |
992 |
| - # This way, if the url just changes, the commits will not |
993 |
| - # have to be re-retrieved |
994 |
| - nn = '__new_origin__' |
995 |
| - smm = sm.module() |
996 |
| - rmts = smm.remotes |
997 |
| - |
998 |
| - # don't do anything if we already have the url we search in place |
999 |
| - if len([r for r in rmts if r.url == sm.url]) == 0: |
1000 |
| - |
1001 |
| - |
1002 |
| - assert nn not in [r.name for r in rmts] |
1003 |
| - smr = smm.create_remote(nn, sm.url) |
1004 |
| - smr.fetch() |
1005 |
| - |
1006 |
| - # If we have a tracking branch, it should be available |
1007 |
| - # in the new remote as well. |
1008 |
| - if len([r for r in smr.refs if r.remote_head == sm.branch.name]) == 0: |
1009 |
| - raise ValueError("Submodule branch named %r was not available in new submodule remote at %r" % (sm.branch.name, sm.url)) |
1010 |
| - # END head is not detached |
1011 |
| - |
1012 |
| - # now delete the changed one |
1013 |
| - rmt_for_deletion = None |
1014 |
| - for remote in rmts: |
1015 |
| - if remote.url == psm.url: |
1016 |
| - rmt_for_deletion = remote |
1017 |
| - break |
1018 |
| - # END if urls match |
1019 |
| - # END for each remote |
1020 |
| - |
1021 |
| - # if we didn't find a matching remote, but have exactly one, |
1022 |
| - # we can safely use this one |
1023 |
| - if rmt_for_deletion is None: |
1024 |
| - if len(rmts) == 1: |
1025 |
| - rmt_for_deletion = rmts[0] |
1026 |
| - else: |
1027 |
| - # if we have not found any remote with the original url |
1028 |
| - # we may not have a name. This is a special case, |
1029 |
| - # and its okay to fail here |
1030 |
| - # Alternatively we could just generate a unique name and leave all |
1031 |
| - # existing ones in place |
1032 |
| - raise InvalidGitRepositoryError("Couldn't find original remote-repo at url %r" % psm.url) |
1033 |
| - #END handle one single remote |
1034 |
| - # END handle check we found a remote |
1035 |
| - |
1036 |
| - orig_name = rmt_for_deletion.name |
1037 |
| - smm.delete_remote(rmt_for_deletion) |
1038 |
| - # NOTE: Currently we leave tags from the deleted remotes |
1039 |
| - # as well as separate tracking branches in the possibly totally |
1040 |
| - # changed repository ( someone could have changed the url to |
1041 |
| - # another project ). At some point, one might want to clean |
1042 |
| - # it up, but the danger is high to remove stuff the user |
1043 |
| - # has added explicitly |
1044 |
| - |
1045 |
| - # rename the new remote back to what it was |
1046 |
| - smr.rename(orig_name) |
1047 |
| - |
1048 |
| - # early on, we verified that the our current tracking branch |
1049 |
| - # exists in the remote. Now we have to assure that the |
1050 |
| - # sha we point to is still contained in the new remote |
1051 |
| - # tracking branch. |
1052 |
| - smsha = sm.binsha |
1053 |
| - found = False |
1054 |
| - rref = smr.refs[self.branch.name] |
1055 |
| - for c in rref.commit.traverse(): |
1056 |
| - if c.binsha == smsha: |
1057 |
| - found = True |
1058 |
| - break |
1059 |
| - # END traverse all commits in search for sha |
1060 |
| - # END for each commit |
1061 |
| - |
1062 |
| - if not found: |
1063 |
| - # adjust our internal binsha to use the one of the remote |
1064 |
| - # this way, it will be checked out in the next step |
1065 |
| - # This will change the submodule relative to us, so |
1066 |
| - # the user will be able to commit the change easily |
1067 |
| - print >> sys.stderr, "WARNING: Current sha %s was not contained in the tracking branch at the new remote, setting it the the remote's tracking branch" % sm.hexsha |
1068 |
| - sm.binsha = rref.commit.binsha |
1069 |
| - #END reset binsha |
1070 |
| - |
1071 |
| - #NOTE: All checkout is performed by the base implementation of update |
1072 |
| - |
1073 |
| - # END skip remote handling if new url already exists in module |
1074 |
| - # END handle url |
1075 |
| - |
1076 |
| - if sm.branch != psm.branch: |
1077 |
| - # finally, create a new tracking branch which tracks the |
1078 |
| - # new remote branch |
1079 |
| - smm = sm.module() |
1080 |
| - smmr = smm.remotes |
1081 |
| - try: |
1082 |
| - tbr = git.Head.create(smm, sm.branch.name) |
1083 |
| - except git.GitCommandError, e: |
1084 |
| - if e.status != 128: |
1085 |
| - raise |
1086 |
| - #END handle something unexpected |
1087 |
| - |
1088 |
| - # ... or reuse the existing one |
1089 |
| - tbr = git.Head(smm, git.Head.to_full_path(sm.branch.name)) |
1090 |
| - #END assure tracking branch exists |
1091 |
| - |
1092 |
| - tbr.set_tracking_branch(find_first_remote_branch(smmr, sm.branch)) |
1093 |
| - # figure out whether the previous tracking branch contains |
1094 |
| - # new commits compared to the other one, if not we can |
1095 |
| - # delete it. |
1096 |
| - try: |
1097 |
| - tbr = find_first_remote_branch(smmr, psm.branch) |
1098 |
| - if len(smm.git.cherry(tbr, psm.branch)) == 0: |
1099 |
| - psm.branch.delete(smm, psm.branch) |
1100 |
| - #END delete original tracking branch if there are no changes |
1101 |
| - except InvalidGitRepositoryError: |
1102 |
| - # ignore it if the previous branch couldn't be found in the |
1103 |
| - # current remotes, this just means we can't handle it |
1104 |
| - pass |
1105 |
| - # END exception handling |
1106 |
| - |
1107 |
| - #NOTE: All checkout is done in the base implementation of update |
1108 |
| - |
1109 |
| - #END handle branch |
1110 |
| - #END handle |
1111 |
| - # END for each common submodule |
1112 |
| - |
1113 |
| - # FINALLY UPDATE ALL ACTUAL SUBMODULES |
1114 |
| - ###################################### |
1115 |
| - for sm in sms: |
1116 |
| - # update the submodule using the default method |
1117 |
| - sm.update(recursive=True, init=init, to_latest_revision=to_latest_revision) |
1118 |
| - |
1119 |
| - # update recursively depth first - question is which inconsitent |
1120 |
| - # state will be better in case it fails somewhere. Defective branch |
1121 |
| - # or defective depth. The RootSubmodule type will never process itself, |
1122 |
| - # which was done in the previous expression |
1123 |
| - if recursive: |
1124 |
| - type(self)(sm.module()).update(recursive=True, force_remove=force_remove, |
1125 |
| - init=init, to_latest_revision=to_latest_revision) |
1126 |
| - #END handle recursive |
1127 |
| - # END for each submodule to update |
1128 | 794 |
|
1129 |
| - def module(self): |
1130 |
| - """:return: the actual repository containing the submodules""" |
1131 |
| - return self.repo |
1132 |
| - #} END interface |
1133 |
| -#} END classes |
0 commit comments