local-to-remote sync is working
[git-mirror.git] / update.py
1 #!/usr/bin/python3
2 import sys, os, subprocess, argparse
3
4 class GitCommand:
5     def __getattr__(self, name):
6         def call(*args, get_stderr = False):
7             cmd = ["git", name.replace('_', '-')] + list(args)
8             output = subprocess.check_output(cmd, stderr=subprocess.STDOUT if get_stderr else None)
9             return output.decode('utf-8').strip('\n')
10         return call
11     
12     def branches(self, *args):
13         b = self.branch(*args).split('\n')
14         b = map(lambda s: s[2:], b)
15         return list(b)
16
17 git = GitCommand()
18
19 def is_all_zero(str):
20     return len(str.replace('0', '')) == 0
21
22 class Repo:
23     def __init__(self, local, mirrors):
24         '''<local> is the directory containing the repository locally, <mirrors> a list of remote repositories'''
25         self.local = local
26         self.mirrors = mirrors
27     
28 #    This is old code, that may be useful again if we decide to care about racy pushes loosing commits.
29 #    def pull(self, slavenr):
30 #        slave = self.slaves[slavenr]
31 #        slavename = "slave-"+str(slavenr)
32 #        # make sure we have the remote
33 #        try:
34 #            git.remote("add", slavename, slave, get_stderr=True)
35 #        except subprocess.CalledProcessError: # the remote already exists
36 #            git.remote("set-url", slavename, slave)
37 #        # get all the changes
38 #        git.fetch(slavename, get_stderr=True)
39 #        # merge them... or hope so...
40 #        branches = git.branches("-r")
41 #        for branch in filter(lambda s: s.startswith(slavename+"/"), branches):
42 #            local = branch[len(slavename+"/"):]
43 #            print(local, branch)
44
45     def update_mirror_ref(self, ref, mirror):
46         '''Update <ref> on <mirror> to the local state. If <newsha> is all-zero, the ref should be deleted.'''
47         git.push('--force', self.mirrors[mirror], ref)
48     
49     def update_ref(self, newsha, ref, source):
50         '''Update the <ref> to <newsha> everywhere. <source> is None if this update comes from the local repository,
51            or the name of a mirror. If <newsha> is all-zero, the ref should be deleted.'''
52         os.chdir(self.local)
53         if source is None:
54             # We already have the latest version locally. Update all the mirrors.
55             for mirror in self.mirrors:
56                 self.update_mirror_ref(ref, mirror)
57         else:
58             raise Exception("Help, what should I do?")
59
60 # for now, configuration is hard-coded here...
61
62 repos = {
63     'sync-test': Repo('/home/git/repositories/test.git', {'github': 'git@github.com:RalfJung/sync-test.git'}),
64 }
65
66 def find_repo_by_directory(dir):
67     for (name, repo) in repos.items():
68         if dir == repo.local:
69             return name
70     return None
71
72 if __name__ == "__main__":
73     parser = argparse.ArgumentParser(description='Keep git repositories in sync')
74     parser.add_argument("--hook",
75                         action="store_true", dest="hook",
76                         help="Act as git hook: Auto-detect the repository based on the working directoy, and fetch information from stdin")
77     parser.add_argument("-r", "--repository",
78                         dest="repository",
79                         help="The name of the repository to act on")
80     args = parser.parse_args()
81     
82     reponame = args.repository
83     if reponame is None and args.hook:
84         reponame = find_repo_by_directory(os.getcwd())
85     if reponame is None:
86         raise Exception("Unable to detect repository, please use --repository.")
87     
88     # now sync this repository
89     repo = repos[reponame]
90     if args.hook:
91         # parse the information we get from stdin
92         for line in sys.stdin:
93             (oldsha, newsha, ref) = line.split()
94             repo.update_ref(newsha, ref, source=None)
95     else:
96         raise Exception("I am unsure what to do here.")