Coverage for gsb/_make_zip_archive.py: 83%

29 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2024-09-08 20:16 +0000

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.""" 

4 

5import datetime as dt 

6import zipfile 

7from time import time 

8 

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

10 

11 

12def write_zip_archive( 

13 repo: Repository, 

14 treeish, 

15 archive: zipfile.ZipFile, 

16 timestamp: int | None = None, 

17 prefix: str = "", 

18) -> None: 

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

20 into an archive. 

21 

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

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

24 

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

26 an empty string. 

27 

28 Parameters 

29 ---------- 

30 repo: Repository 

31 The git repository 

32 treeish 

33 The treeish to write 

34 archive : zipfile.ZipFile 

35 An archive from the 'zipfile' module. 

36 timestamp : int, optional 

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

38 prefix : str, optional 

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

40 

41 Notes 

42 ----- 

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

44 between `TarInfo` and `ZipInfo`. 

45 

46 Example 

47 ------- 

48 >>> import tarfile, pygit2 

49 >>> from gsb import _git 

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

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

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

53 """ 

54 # Try to get a tree form whatever we got 

55 # Try to get a tree form whatever we got 

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

57 treeish = repo[treeish] 

58 

59 tree = treeish.peel(Tree) 

60 

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

62 if not timestamp: 

63 try: 

64 commit = treeish.peel(Commit) 

65 timestamp = commit.committer.time 

66 except Exception: 

67 pass 

68 

69 # as a last resort, use the current timestamp 

70 if not timestamp: 

71 timestamp = int(time()) 

72 

73 datetime = dt.datetime.fromtimestamp(timestamp) 

74 zip_datetime = ( 

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

76 datetime.year, 

77 datetime.month, 

78 datetime.day, 

79 datetime.hour, 

80 datetime.minute, 

81 datetime.second, 

82 ) 

83 

84 index = Index() 

85 index.read_tree(tree) 

86 

87 for entry in index: 

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

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

90 info.file_size = len(content) 

91 info.date_time = zip_datetime 

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

93 if entry.mode == GIT_FILEMODE_LINK: 

94 raise NotImplementedError( 

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

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

97 ) 

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

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

100 archive.writestr(info, content)