Coverage for gsb/_make_zip_archive.py: 83%

29 statements  

« prev     ^ index     » next       coverage.py v7.2.6, created at 2024-09-08 16:23 -0400

1"""ZIP-compatible `repo.write_archive`, stand-alone for easier upstream sharing. 

2As it is adapted directly from pygit2, this module is licensed under the 

3GNU Public License v2.""" 

4import datetime as dt 

5import zipfile 

6from time import time 

7 

8from pygit2 import GIT_FILEMODE_LINK, Commit, Index, Oid, Repository, Tree 

9 

10 

11def write_zip_archive( 

12 repo: Repository, 

13 treeish, 

14 archive: zipfile.ZipFile, 

15 timestamp: int | None = None, 

16 prefix: str = "", 

17) -> None: 

18 """Implementation of `repo.write_archive` that supports ZIP. Writes treeish 

19 into an archive. 

20 

21 If no timestamp is provided and 'treeish' is a commit, its committer 

22 timestamp will be used. Otherwise the current time will be used. 

23 

24 All path names in the archive are added to 'prefix', which defaults to 

25 an empty string. 

26 

27 Parameters 

28 ---------- 

29 repo: Repository 

30 The git repository 

31 treeish 

32 The treeish to write 

33 archive : zipfile.ZipFile 

34 An archive from the 'zipfile' module. 

35 timestamp : int, optional 

36 (Epoch) timestamp to use for the files in the archive. 

37 prefix : str, optional 

38 Extra prefix to add to the path names in the archive. 

39 

40 Notes 

41 ----- 

42 h/t to https://stackoverflow.com/a/18432983 for the example on converting 

43 between `TarInfo` and `ZipInfo`. 

44 

45 Example 

46 ------- 

47 >>> import tarfile, pygit2 

48 >>> from gsb import _git 

49 >>> repo = pygit2.Repository('.') 

50 >>> with zipfile.ZipFile('foo.zip', 'w') as archive: 

51 ... _git.write_zip_archive(repo, repo.head.target, archive) 

52 """ 

53 # Try to get a tree form whatever we got 

54 # Try to get a tree form whatever we got 

55 if isinstance(treeish, (str, Oid)): 

56 treeish = repo[treeish] 

57 

58 tree = treeish.peel(Tree) 

59 

60 # if we don't have a timestamp, try to get it from a commit 

61 if not timestamp: 

62 try: 

63 commit = treeish.peel(Commit) 

64 timestamp = commit.committer.time 

65 except Exception: 

66 pass 

67 

68 # as a last resort, use the current timestamp 

69 if not timestamp: 

70 timestamp = int(time()) 

71 

72 datetime = dt.datetime.fromtimestamp(timestamp) 

73 zip_datetime = ( 

74 # per https://docs.python.org/3/library/zipfile.html#zipfile.ZipInfo.date_time 

75 datetime.year, 

76 datetime.month, 

77 datetime.day, 

78 datetime.hour, 

79 datetime.minute, 

80 datetime.second, 

81 ) 

82 

83 index = Index() 

84 index.read_tree(tree) 

85 

86 for entry in index: 

87 content = repo[entry.id].read_raw() 

88 info = zipfile.ZipInfo(prefix + entry.path) 

89 info.file_size = len(content) 

90 info.date_time = zip_datetime 

91 # info.uname = info.gname = "root" # git's archive-zip.c does not 

92 if entry.mode == GIT_FILEMODE_LINK: 

93 raise NotImplementedError( 

94 "ZIP archives with symlinks are not currently supported." 

95 "\nSee: https://bugs.python.org/issue37921" 

96 ) 

97 # per https://github.com/git/git/blob/3a06386e/archive-zip.c#L339 

98 info.external_attr = entry.mode << 16 if (entry.mode & 111) else 0 

99 archive.writestr(info, content)