|
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