forked from mozilla-conduit/lando-api
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathphabricator_patch.py
142 lines (122 loc) · 4.16 KB
/
phabricator_patch.py
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
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
# Largely inspired by moz-phab:
# https://github.com/mozilla-conduit/review/blob/1.40/moz-phab#L1187
import enum
import rs_parsepatch
class FileType(enum.Enum):
TEXT = 1
IMAGE = 2
BINARY = 3
DIRECTORY = 4 # Should never show up...
SYMLINK = 5 # Su
DELETED = 6
NORMAL = 7
class ChangeKind(enum.Enum):
ADD = 1
CHANGE = 2
DELETE = 3
MOVE_AWAY = 4
COPY_AWAY = 5
MOVE_HERE = 6
COPY_HERE = 7
MULTICOPY = 8
def serialize_hunk(hunk: list) -> dict:
prev_op = " "
old_eof_newline, new_eof_newline = True, True
corpus = []
olds = [l[0] for l in hunk if l[0] is not None]
news = [l[1] for l in hunk if l[1] is not None]
add_lines, del_lines = 0, 0
for (old, new, line) in hunk:
line = line.decode("utf-8")
# Rebuild each line as patch
op = " "
if old is None and new is not None:
op = "+"
add_lines += 1
elif old is not None and new is None:
op = "-"
del_lines += 1
corpus.append(f"{op}{line}")
# Detect end of lines
if line.endswith("No newline at end of file"):
if prev_op != "+":
old_eof_newline = False
if prev_op != "-":
new_eof_newline = False
prev_op = op
return {
"oldOffset": olds[0] if olds else 0,
"oldLength": olds[-1] - olds[0] + 1 if olds else 0,
"newOffset": news[0] if news else 0,
"newLength": news[-1] - news[0] + 1 if news else 0,
"addLines": add_lines,
"delLines": del_lines,
"isMissingOldNewline": not old_eof_newline,
"isMissingNewNewline": not new_eof_newline,
"corpus": "\n".join(corpus),
}
def unix_file_mode(value: int) -> str:
"""Convert a uint32_t into base 8 for unix file modes"""
return "{:06o}".format(value)
def serialize_patched_file(f: dict, public_node: str) -> dict:
# Detect binary or test (not images)
metadata = {}
if f["binary"] is True:
# We cannot detect the mime type from a file in the patch
# So no support for image file type
file_type = FileType.BINARY
# Add binary metadata
for upload_type in ("old", "new"):
metadata[f"{upload_type}:binary-phid"] = None
# TODO support metadata[f"{upload_type}:file:size"]
# See https://github.com/mozilla/pyo3-parsepatch/issues/11
else:
file_type = FileType.TEXT
# Detect change kind
old_path = None
if f["new"] is True:
change_kind = ChangeKind.ADD
elif f["deleted"] is True:
change_kind = ChangeKind.DELETE
old_path = f["filename"]
elif f["copied_from"] is not None:
change_kind = ChangeKind.COPY_HERE
old_path = f["copied_from"]
elif f["renamed_from"] is not None:
change_kind = ChangeKind.MOVE_HERE
old_path = f["renamed_from"]
else:
change_kind = ChangeKind.CHANGE
old_path = f["filename"]
# File modes
old_props = (
{"unix:filemode": unix_file_mode(f["modes"]["old"])}
if "old" in f["modes"]
else {}
)
new_props = (
{"unix:filemode": unix_file_mode(f["modes"]["new"])}
if "new" in f["modes"]
else {}
)
return {
"metadata": metadata,
"oldPath": old_path,
"currentPath": f["filename"],
"awayPaths": [old_path]
if change_kind in (ChangeKind.COPY_HERE, ChangeKind.MOVE_HERE)
else [],
"commitHash": public_node,
"type": change_kind.value,
"fileType": file_type.value,
"hunks": [serialize_hunk(hunk) for hunk in f["hunks"]],
"oldProperties": old_props,
"newProperties": new_props,
}
def patch_to_changes(patch_content: str, public_node: str) -> list:
"""Build a list of Phabricator changes from a raw diff"""
patch = rs_parsepatch.get_diffs(patch_content, hunks=True)
return [serialize_patched_file(f, public_node) for f in patch]