File: export_sparse_subrepo.py

package info (click to toggle)
cmake-format 0.6.13-7
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 3,436 kB
  • sloc: python: 16,990; makefile: 14
file content (147 lines) | stat: -rw-r--r-- 4,046 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
"""
Synchronize or validate a sparse subrepository export. The sparse export
configuration is stored in the `.sparse-export` file in the root of the
repository. If the file does not exist, then there is nothing to update
or verify.
"""

import argparse
import hashlib
import logging
import os
import shutil
import subprocess
import sys

logger = logging.getLogger(__name__)


def setup_argparser(argparser):
  argparser.add_argument(
      "--log-level", default="warning",
      choices=["debug", "info", "warning", "error"])
  argparser.add_argument(
      "command", choices=["update", "verify"],
      help="whether to update or verify the export")
  argparser.add_argument("--force")


def get_argdict(args):
  out = {}
  for key, value in vars(args).items():
    if key.startswith("_"):
      continue
    out[key] = value
  return out


def copy_file(sourcepath, destpath):
  destdir = os.path.dirname(destpath)
  if not os.path.exists(destdir):
    logger.debug("Making directory %s", destdir)
    os.makedirs(destdir)
  logger.debug("Copying %s -> %s", sourcepath, destpath)
  shutil.copy2(sourcepath, destpath)
  return 0


def read_chunks(infile, chunksize=4096):
  while True:
    chunk = infile.read(chunksize)
    if chunk == b'':
      break
    yield chunk


def hash_file(filepath, hashname="sha1", chunksize=4096):
  hashobj = hashlib.new(hashname)
  with open(filepath, "rb") as infile:
    for chunk in read_chunks(infile):
      hashobj.update(chunk)
  return hashobj


def verify_file(sourcepath, destpath):
  if not os.path.exists(destpath):
    logger.error("Missing %s", destpath)
    return 1

  if hash_file(sourcepath).digest() == hash_file(destpath).digest():
    logger.info("File %s up to date", destpath)
    return 0
  logger.error("File %s differs from source", destpath)
  return 1


def inner_main(command, force):
  if command == "update":
    operation = copy_file
  elif command == "verify":
    operation = verify_file
  else:
    raise ValueError("Unknown command {}".format(command))

  repodir = subprocess.check_output(
      ["git", "rev-parse", "--show-toplevel"]).strip().decode("utf-8")
  exportsdir = os.path.join(repodir, "tangent/tooling/sparse-exports")

  if force:
    logger.debug("Forcing export of %s", force)
    dirname = force
  else:
    if os.path.exists(os.path.join(exportsdir, "iamgroot.txt")):
      # This is the monorepo
      return 0

    sparse_sentinel = os.path.join(repodir, ".sparse-export")
    if not os.path.exists(exportsdir) and os.path.exists(sparse_sentinel):
      # This is the public export repo
      return 0

    dirnames = os.listdir(exportsdir)
    if len(dirnames) != 1:
      logger.error(
          "Invalid sparse export, %s contains too many directories", exportsdir)
      return 1
    dirname = dirnames[0]

  export_dir = os.path.join(exportsdir, dirname)
  returncode = 0
  for directory, _dirnames, filenames in os.walk(export_dir):
    reldir = os.path.relpath(directory, export_dir)
    for filename in filenames:
      if reldir == ".":
        relpath_file = filename
      else:
        relpath_file = os.path.join(reldir, filename)

      sourcepath = os.path.join(directory, filename)
      if relpath_file == "sparse-checkout":
        gitdir = subprocess.check_output(
            ["git", "rev-parse", "--git-dir"]).decode("utf-8").strip()
        destpath = os.path.join(gitdir, "info/sparse-checkout")
      else:
        destpath = os.path.join(repodir, relpath_file)

      returncode |= operation(sourcepath, destpath)
  return returncode


def main():
  logging.basicConfig(level=logging.INFO)
  argparser = argparse.ArgumentParser(description=__doc__)
  setup_argparser(argparser)
  try:
    import argcomplete
    argcomplete.autocomplete(argparser)
  except ImportError:
    pass
  args = argparser.parse_args()
  argdict = get_argdict(args)
  log_level = argdict.pop("log_level")
  logging.getLogger().setLevel(getattr(logging, log_level.upper()))
  return inner_main(**argdict)


if __name__ == "__main__":
  sys.exit(main())