diffmanifests 1.2.0__py2.py3-none-any.whl → 3.4.1__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of diffmanifests might be problematic. Click here for more details.
- diffmanifests/cmd/argument.py +1 -2
- diffmanifests/cmd/version.py +1 -1
- diffmanifests/config/config.json +10 -0
- diffmanifests/differ/differ.py +22 -20
- diffmanifests/gerrit/__init__.py +0 -0
- diffmanifests/gerrit/gerrit.py +51 -0
- diffmanifests/gitiles/gitiles.py +27 -8
- diffmanifests/printer/printer.py +38 -10
- diffmanifests/proto/proto.py +26 -15
- diffmanifests/querier/querier.py +138 -44
- diffmanifests-3.4.1.dist-info/METADATA +422 -0
- diffmanifests-3.4.1.dist-info/RECORD +27 -0
- {diffmanifests-1.2.0.dist-info → diffmanifests-3.4.1.dist-info}/WHEEL +1 -1
- {diffmanifests-1.2.0.dist-info → diffmanifests-3.4.1.dist-info}/entry_points.txt +0 -1
- diffmanifests-1.2.0.dist-info/METADATA +0 -133
- diffmanifests-1.2.0.dist-info/RECORD +0 -25
- {diffmanifests-1.2.0.dist-info → diffmanifests-3.4.1.dist-info/licenses}/LICENSE +0 -0
- {diffmanifests-1.2.0.dist-info → diffmanifests-3.4.1.dist-info}/top_level.txt +0 -0
diffmanifests/cmd/argument.py
CHANGED
|
@@ -8,8 +8,7 @@ from .version import VERSION
|
|
|
8
8
|
|
|
9
9
|
class Argument(object):
|
|
10
10
|
def __init__(self):
|
|
11
|
-
self._parser = argparse.ArgumentParser(description=''
|
|
12
|
-
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
|
11
|
+
self._parser = argparse.ArgumentParser(description='Diff Manifests')
|
|
13
12
|
self._add()
|
|
14
13
|
|
|
15
14
|
def _add(self):
|
diffmanifests/cmd/version.py
CHANGED
diffmanifests/config/config.json
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
{
|
|
2
|
+
"gerrit": {
|
|
3
|
+
"pass": "",
|
|
4
|
+
"query": {
|
|
5
|
+
"option": ["CURRENT_REVISION"]
|
|
6
|
+
},
|
|
7
|
+
"url": "https://android-review.googlesource.com",
|
|
8
|
+
"user": ""
|
|
9
|
+
},
|
|
2
10
|
"gitiles": {
|
|
3
11
|
"pass": "",
|
|
12
|
+
"retry": 1,
|
|
13
|
+
"timeout": -1,
|
|
4
14
|
"url": "https://android.googlesource.com",
|
|
5
15
|
"user": ""
|
|
6
16
|
}
|
diffmanifests/differ/differ.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
|
|
3
|
-
from ..proto.proto import
|
|
3
|
+
from ..proto.proto import Label, Repo
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
class DifferException(Exception):
|
|
@@ -49,27 +49,23 @@ class Differ(object):
|
|
|
49
49
|
for item in project2:
|
|
50
50
|
name2.append(item['@name'])
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
buf = list(set(
|
|
52
|
+
added = {}
|
|
53
|
+
buf = list(set(name2).difference(set(name1)))
|
|
54
54
|
for item in buf:
|
|
55
|
-
revision1, upstream1 = _helper(data1, project1, item)
|
|
56
55
|
revision2, upstream2 = _helper(data2, project2, item)
|
|
57
|
-
|
|
58
|
-
{
|
|
59
|
-
Repo.BRANCH: upstream1,
|
|
60
|
-
Repo.COMMIT: revision1
|
|
61
|
-
},
|
|
56
|
+
added[item] = [
|
|
57
|
+
{},
|
|
62
58
|
{
|
|
63
59
|
Repo.BRANCH: upstream2,
|
|
64
60
|
Repo.COMMIT: revision2
|
|
65
61
|
}
|
|
66
62
|
]
|
|
67
63
|
|
|
68
|
-
|
|
64
|
+
removed = {}
|
|
69
65
|
buf = list(set(name1).difference(set(name2)))
|
|
70
66
|
for item in buf:
|
|
71
67
|
revision1, upstream1 = _helper(data1, project1, item)
|
|
72
|
-
|
|
68
|
+
removed[item] = [
|
|
73
69
|
{
|
|
74
70
|
Repo.BRANCH: upstream1,
|
|
75
71
|
Repo.COMMIT: revision1
|
|
@@ -77,19 +73,25 @@ class Differ(object):
|
|
|
77
73
|
{}
|
|
78
74
|
]
|
|
79
75
|
|
|
80
|
-
|
|
81
|
-
buf = list(set(
|
|
76
|
+
updated = {}
|
|
77
|
+
buf = list(set(name1).intersection(set(name2)))
|
|
82
78
|
for item in buf:
|
|
79
|
+
revision1, upstream1 = _helper(data1, project1, item)
|
|
83
80
|
revision2, upstream2 = _helper(data2, project2, item)
|
|
84
|
-
|
|
85
|
-
|
|
81
|
+
if revision1 == revision2:
|
|
82
|
+
continue
|
|
83
|
+
updated[item] = [
|
|
84
|
+
{
|
|
85
|
+
Repo.BRANCH: upstream1,
|
|
86
|
+
Repo.COMMIT: revision1
|
|
87
|
+
},
|
|
86
88
|
{
|
|
87
89
|
Repo.BRANCH: upstream2,
|
|
88
90
|
Repo.COMMIT: revision2
|
|
89
91
|
}
|
|
90
92
|
]
|
|
91
93
|
|
|
92
|
-
return
|
|
94
|
+
return added, removed, updated
|
|
93
95
|
|
|
94
96
|
def run(self, data1, data2):
|
|
95
97
|
if 'manifest' not in data1 or 'manifest' not in data2:
|
|
@@ -104,10 +106,10 @@ class Differ(object):
|
|
|
104
106
|
if 'remote' not in data1['manifest'] or 'remote' not in data2['manifest']:
|
|
105
107
|
raise DifferException('remote invalid')
|
|
106
108
|
|
|
107
|
-
|
|
109
|
+
added, removed, updated = self._diff(data1, data2)
|
|
108
110
|
|
|
109
111
|
return {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
112
|
+
Label.ADD_REPO: added,
|
|
113
|
+
Label.REMOVE_REPO: removed,
|
|
114
|
+
Label.UPDATE_REPO: updated
|
|
113
115
|
}
|
|
File without changes
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import requests
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class GerritException(Exception):
|
|
8
|
+
def __init__(self, info):
|
|
9
|
+
super().__init__(self)
|
|
10
|
+
self._info = info
|
|
11
|
+
|
|
12
|
+
def __str__(self):
|
|
13
|
+
return self._info
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Gerrit(object):
|
|
17
|
+
def __init__(self, config):
|
|
18
|
+
if config is None:
|
|
19
|
+
raise GerritException('Invalid gerrit config')
|
|
20
|
+
self._pass = config['gerrit'].get('pass', '')
|
|
21
|
+
self._query = config['gerrit'].get('query', {'option': ['CURRENT_REVISION']})
|
|
22
|
+
self._user = config['gerrit'].get('user', '')
|
|
23
|
+
self._url = config['gerrit'].get('url', 'localhost:80')
|
|
24
|
+
if len(self._pass) != 0 and len(self._user) != 0:
|
|
25
|
+
self._url += '/a'
|
|
26
|
+
|
|
27
|
+
def get(self, _id):
|
|
28
|
+
if len(self._pass) != 0 and len(self._user) != 0:
|
|
29
|
+
response = requests.get(url=self._url+'/changes/'+str(_id)+'/detail', auth=(self._user, self._pass))
|
|
30
|
+
else:
|
|
31
|
+
response = requests.get(url=self._url+'/changes/'+str(_id)+'/detail')
|
|
32
|
+
if response.status_code != requests.codes.ok:
|
|
33
|
+
return None
|
|
34
|
+
return json.loads(response.text.replace(")]}'", ''))
|
|
35
|
+
|
|
36
|
+
def query(self, search, start):
|
|
37
|
+
payload = {
|
|
38
|
+
'o': self._query['option'],
|
|
39
|
+
'q': search,
|
|
40
|
+
'start': start
|
|
41
|
+
}
|
|
42
|
+
if len(self._pass) != 0 and len(self._user) != 0:
|
|
43
|
+
response = requests.get(url=self._url+'/changes/', auth=(self._user, self._pass), params=payload)
|
|
44
|
+
else:
|
|
45
|
+
response = requests.get(url=self._url+'/changes/', params=payload)
|
|
46
|
+
if response.status_code != requests.codes.ok:
|
|
47
|
+
return None
|
|
48
|
+
return json.loads(response.text.replace(")]}'", ''))
|
|
49
|
+
|
|
50
|
+
def url(self):
|
|
51
|
+
return self._url
|
diffmanifests/gitiles/gitiles.py
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
import json
|
|
4
4
|
import requests
|
|
5
5
|
|
|
6
|
+
from requests.adapters import HTTPAdapter
|
|
7
|
+
|
|
6
8
|
|
|
7
9
|
class GitilesException(Exception):
|
|
8
10
|
def __init__(self, info):
|
|
@@ -18,28 +20,45 @@ class Gitiles(object):
|
|
|
18
20
|
if config is None or config.get('gitiles', None) is None:
|
|
19
21
|
raise GitilesException('config invalid')
|
|
20
22
|
self._pass = config['gitiles'].get('pass', '')
|
|
23
|
+
self._retry = config['gitiles'].get('retry', 0)
|
|
24
|
+
self._retry = self._retry if self._retry >= 0 else 0
|
|
25
|
+
self._timeout = config['gitiles'].get('timeout', -1)
|
|
26
|
+
self._timeout = self._timeout if self._timeout >= 0 else None
|
|
21
27
|
self._url = config['gitiles'].get('url', 'http://localhost:80').rstrip('/')
|
|
22
28
|
self._user = config['gitiles'].get('user', '')
|
|
23
29
|
|
|
24
30
|
def commit(self, repo, commit):
|
|
31
|
+
session = requests.Session()
|
|
32
|
+
session.keep_alive = False
|
|
33
|
+
session.mount('http://', HTTPAdapter(max_retries=self._retry))
|
|
34
|
+
session.mount('https://', HTTPAdapter(max_retries=self._retry))
|
|
25
35
|
if len(self._pass) == 0 or len(self._user) == 0:
|
|
26
|
-
response =
|
|
36
|
+
response = session.get(url=self._url + '/%s/+/%s?format=JSON' % (repo, commit), timeout=self._timeout)
|
|
27
37
|
else:
|
|
28
|
-
response =
|
|
29
|
-
|
|
38
|
+
response = session.get(url=self._url + '/%s/+/%s?format=JSON' % (repo, commit),
|
|
39
|
+
auth=(self._user, self._pass), timeout=self._timeout)
|
|
40
|
+
session.close()
|
|
30
41
|
if response.status_code != requests.codes.ok:
|
|
31
42
|
return None
|
|
32
|
-
|
|
43
|
+
ret = json.loads(response.text.replace(")]}'", ''))
|
|
44
|
+
return ret
|
|
33
45
|
|
|
34
46
|
def commits(self, repo, branch, commit):
|
|
47
|
+
session = requests.Session()
|
|
48
|
+
session.keep_alive = False
|
|
49
|
+
session.mount('http://', HTTPAdapter(max_retries=self._retry))
|
|
50
|
+
session.mount('https://', HTTPAdapter(max_retries=self._retry))
|
|
35
51
|
if len(self._pass) == 0 or len(self._user) == 0:
|
|
36
|
-
response =
|
|
52
|
+
response = session.get(url=self._url + '/%s/+log/%s/?s=%s&format=JSON' % (repo, branch, commit),
|
|
53
|
+
timeout=self._timeout)
|
|
37
54
|
else:
|
|
38
|
-
response =
|
|
39
|
-
|
|
55
|
+
response = session.get(url=self._url + '/%s/+log/%s/?s=%s&format=JSON' % (repo, branch, commit),
|
|
56
|
+
auth=(self._user, self._pass), timeout=self._timeout)
|
|
57
|
+
session.close()
|
|
40
58
|
if response.status_code != requests.codes.ok:
|
|
41
59
|
return None
|
|
42
|
-
|
|
60
|
+
ret = json.loads(response.text.replace(")]}'", ''))
|
|
61
|
+
return ret
|
|
43
62
|
|
|
44
63
|
def url(self):
|
|
45
64
|
return self._url
|
diffmanifests/printer/printer.py
CHANGED
|
@@ -3,10 +3,15 @@
|
|
|
3
3
|
import json
|
|
4
4
|
import openpyxl
|
|
5
5
|
import os
|
|
6
|
+
import re
|
|
6
7
|
import time
|
|
7
8
|
|
|
9
|
+
from openpyxl.styles import Alignment, Font
|
|
8
10
|
from ..proto.proto import Commit
|
|
9
11
|
|
|
12
|
+
# Refer: openpyxl/cell/cell.py
|
|
13
|
+
ILLEGAL_CHARACTERS_RE = re.compile(r'[\000-\010]|[\013-\014]|[\016-\037]')
|
|
14
|
+
|
|
10
15
|
head = {
|
|
11
16
|
'A': Commit.DIFF,
|
|
12
17
|
'B': Commit.REPO,
|
|
@@ -15,7 +20,10 @@ head = {
|
|
|
15
20
|
'E': Commit.DATE,
|
|
16
21
|
'F': Commit.COMMIT,
|
|
17
22
|
'G': Commit.MESSAGE,
|
|
18
|
-
'H': Commit.URL
|
|
23
|
+
'H': Commit.URL,
|
|
24
|
+
'I': Commit.CHANGE,
|
|
25
|
+
'J': Commit.COMMITTER,
|
|
26
|
+
'K': Commit.TOPIC
|
|
19
27
|
}
|
|
20
28
|
|
|
21
29
|
|
|
@@ -46,14 +54,17 @@ class Printer(object):
|
|
|
46
54
|
def _txt(self, data, name):
|
|
47
55
|
def _txt_helper(data, out):
|
|
48
56
|
global head
|
|
49
|
-
out.write(u'%s%s: %s\n' % (' '*
|
|
50
|
-
out.write(u'%s%s: %s\n' % (' '*
|
|
51
|
-
out.write(u'%s%s: %s\n' % (' '*
|
|
52
|
-
out.write(u'%s%s: %s\n' % (' '*
|
|
53
|
-
out.write(u'%s%s: %s\n' % (' '*
|
|
54
|
-
out.write(u'%s%s: %s\n' % (' '*
|
|
55
|
-
out.write(u'%s%s: %s\n' % (' '*
|
|
56
|
-
out.write(u'%s%s: %s\n' % (' '*
|
|
57
|
+
out.write(u'%s%s: %s\n' % (' '*5, head['A'], data[head['A']]))
|
|
58
|
+
out.write(u'%s%s: %s\n' % (' '*5, head['B'], data[head['B']]))
|
|
59
|
+
out.write(u'%s%s: %s\n' % (' '*3, head['C'], data[head['C']]))
|
|
60
|
+
out.write(u'%s%s: %s\n' % (' '*3, head['D'], data[head['D']]))
|
|
61
|
+
out.write(u'%s%s: %s\n' % (' '*5, head['E'], data[head['E']]))
|
|
62
|
+
out.write(u'%s%s: %s\n' % (' '*3, head['F'], data[head['F']]))
|
|
63
|
+
out.write(u'%s%s: %s\n' % (' '*2, head['G'], data[head['G']]))
|
|
64
|
+
out.write(u'%s%s: %s\n' % (' '*6, head['H'], data[head['H']]))
|
|
65
|
+
out.write(u'%s%s: %s\n' % (' '*3, head['I'], data[head['I']]))
|
|
66
|
+
out.write(u'%s%s: %s\n' % (' '*0, head['J'], data[head['J']]))
|
|
67
|
+
out.write(u'%s%s: %s\n' % (' '*4, head['K'], data[head['K']]))
|
|
57
68
|
out.write('\n')
|
|
58
69
|
|
|
59
70
|
with open(name, 'w', encoding='utf8') as f:
|
|
@@ -62,12 +73,29 @@ class Printer(object):
|
|
|
62
73
|
_txt_helper(item, f)
|
|
63
74
|
|
|
64
75
|
def _xlsx(self, data, name):
|
|
76
|
+
def _styling_head(sheet):
|
|
77
|
+
for item in head.keys():
|
|
78
|
+
sheet[item+'1'].alignment = Alignment(horizontal='center', shrink_to_fit=True, vertical='center')
|
|
79
|
+
sheet[item+'1'].font = Font(bold=True, name='Calibri')
|
|
80
|
+
sheet.freeze_panes = sheet['B2']
|
|
81
|
+
|
|
82
|
+
def _styling_data(sheet, rows):
|
|
83
|
+
for key in head.keys():
|
|
84
|
+
for row in range(rows):
|
|
85
|
+
sheet[key+str(row+2)].alignment = Alignment(vertical='center')
|
|
86
|
+
sheet[key+str(row+2)].font = Font(bold=False, name='Calibri')
|
|
87
|
+
|
|
65
88
|
wb = openpyxl.Workbook()
|
|
66
89
|
ws = wb.active
|
|
67
90
|
ws.title = time.strftime('%Y-%m-%d', time.localtime(time.time()))
|
|
68
91
|
ws.append([head[key].upper() for key in sorted(head.keys())])
|
|
69
92
|
for item in data:
|
|
70
|
-
|
|
93
|
+
buf = []
|
|
94
|
+
for key in sorted(head.keys()):
|
|
95
|
+
buf.append(re.sub(ILLEGAL_CHARACTERS_RE, ' ', item[head[key]]))
|
|
96
|
+
ws.append(buf)
|
|
97
|
+
_styling_head(ws)
|
|
98
|
+
_styling_data(ws, len(data))
|
|
71
99
|
wb.save(filename=name)
|
|
72
100
|
|
|
73
101
|
def run(self, data, name):
|
diffmanifests/proto/proto.py
CHANGED
|
@@ -3,33 +3,37 @@
|
|
|
3
3
|
|
|
4
4
|
"""Prototype
|
|
5
5
|
{
|
|
6
|
-
"
|
|
6
|
+
"add repo": {
|
|
7
7
|
"REPO": [
|
|
8
|
+
{},
|
|
8
9
|
{
|
|
9
10
|
"branch": BRANCH,
|
|
10
|
-
"commit": COMMIT
|
|
11
|
-
|
|
12
|
-
{
|
|
13
|
-
"branch": BRANCH,
|
|
14
|
-
"commit": COMMIT
|
|
11
|
+
"commit": COMMIT,
|
|
12
|
+
"diff": "add commit"
|
|
15
13
|
}
|
|
16
14
|
]
|
|
17
15
|
},
|
|
18
|
-
"
|
|
16
|
+
"remove repo": {
|
|
19
17
|
"REPO": [
|
|
20
18
|
{
|
|
21
19
|
"branch": BRANCH,
|
|
22
|
-
"commit": COMMIT
|
|
20
|
+
"commit": COMMIT,
|
|
21
|
+
"diff": "remove commit"
|
|
23
22
|
},
|
|
24
23
|
{}
|
|
25
24
|
]
|
|
26
25
|
},
|
|
27
|
-
"
|
|
26
|
+
"update repo": {
|
|
28
27
|
"REPO": [
|
|
29
|
-
{},
|
|
30
28
|
{
|
|
31
29
|
"branch": BRANCH,
|
|
32
|
-
"commit": COMMIT
|
|
30
|
+
"commit": COMMIT,
|
|
31
|
+
"diff": "add commit"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"branch": BRANCH,
|
|
35
|
+
"commit": COMMIT,
|
|
36
|
+
"diff": "remove commit"
|
|
33
37
|
}
|
|
34
38
|
]
|
|
35
39
|
}
|
|
@@ -40,20 +44,27 @@
|
|
|
40
44
|
class Commit:
|
|
41
45
|
AUTHOR = 'author'
|
|
42
46
|
BRANCH = 'branch'
|
|
47
|
+
CHANGE = 'change'
|
|
43
48
|
COMMIT = 'commit'
|
|
49
|
+
COMMITTER = 'committer'
|
|
44
50
|
DATE = 'date'
|
|
45
51
|
DIFF = 'diff'
|
|
52
|
+
HASHTAGS = 'hashtags'
|
|
46
53
|
MESSAGE = 'message'
|
|
47
54
|
REPO = 'repo'
|
|
55
|
+
TOPIC = 'topic'
|
|
48
56
|
URL = 'url'
|
|
49
57
|
|
|
50
58
|
|
|
51
|
-
class
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
59
|
+
class Label:
|
|
60
|
+
ADD_COMMIT = 'add commit'
|
|
61
|
+
ADD_REPO = 'add repo'
|
|
62
|
+
REMOVE_COMMIT = 'remove commit'
|
|
63
|
+
REMOVE_REPO = 'remove repo'
|
|
64
|
+
UPDATE_REPO = 'update repo'
|
|
55
65
|
|
|
56
66
|
|
|
57
67
|
class Repo:
|
|
58
68
|
BRANCH = 'branch'
|
|
59
69
|
COMMIT = 'commit'
|
|
70
|
+
DIFF = 'diff'
|
diffmanifests/querier/querier.py
CHANGED
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
import datetime
|
|
4
4
|
|
|
5
|
+
from ..gerrit.gerrit import Gerrit
|
|
5
6
|
from ..gitiles.gitiles import Gitiles
|
|
6
|
-
from ..
|
|
7
|
+
from ..logger.logger import Logger
|
|
8
|
+
from ..proto.proto import Commit, Label, Repo
|
|
7
9
|
|
|
8
10
|
|
|
9
11
|
class QuerierException(Exception):
|
|
@@ -19,23 +21,64 @@ class Querier(object):
|
|
|
19
21
|
def __init__(self, config=None):
|
|
20
22
|
if config is None:
|
|
21
23
|
raise QuerierException('config invalid')
|
|
24
|
+
self.gerrit = Gerrit(config)
|
|
22
25
|
self.gitiles = Gitiles(config)
|
|
23
26
|
|
|
24
27
|
def _build(self, repo, branch, commit, label):
|
|
28
|
+
def _query(commit):
|
|
29
|
+
buf = self.gerrit.query('commit:' + commit, 0)
|
|
30
|
+
if buf is None or len(buf) != 1:
|
|
31
|
+
return '', '', []
|
|
32
|
+
|
|
33
|
+
# Construct the change URL based on Gerrit instance type
|
|
34
|
+
gerrit_url = self.gerrit.url()
|
|
35
|
+
# Remove /a suffix if present (used for authenticated access)
|
|
36
|
+
if gerrit_url.endswith('/a'):
|
|
37
|
+
gerrit_url = gerrit_url[:-2]
|
|
38
|
+
|
|
39
|
+
change_number = str(buf[0]['_number'])
|
|
40
|
+
|
|
41
|
+
# For self-hosted Gerrit (non-googlesource), add /c/ prefix
|
|
42
|
+
if 'googlesource.com' not in gerrit_url:
|
|
43
|
+
change_url = gerrit_url + '/c/' + repo + '/+/' + change_number
|
|
44
|
+
else:
|
|
45
|
+
change_url = gerrit_url + '/' + change_number
|
|
46
|
+
|
|
47
|
+
return change_url, buf[0].get('topic', ''), buf[0].get('hashtags', [])
|
|
48
|
+
|
|
49
|
+
change, topic, hashtags = _query(commit['commit'])
|
|
25
50
|
return [{
|
|
26
51
|
Commit.AUTHOR: '%s <%s>' % (commit['author']['name'], commit['author']['email']),
|
|
27
52
|
Commit.BRANCH: branch,
|
|
53
|
+
Commit.CHANGE: change,
|
|
28
54
|
Commit.COMMIT: commit['commit'],
|
|
55
|
+
Commit.COMMITTER: '%s <%s>' % (commit['committer']['name'], commit['committer']['email']),
|
|
29
56
|
Commit.DATE: commit['author']['time'],
|
|
30
57
|
Commit.DIFF: label.upper(),
|
|
31
|
-
Commit.
|
|
58
|
+
Commit.HASHTAGS: hashtags,
|
|
59
|
+
Commit.MESSAGE: commit['message'].strip(),
|
|
32
60
|
Commit.REPO: repo,
|
|
61
|
+
Commit.TOPIC: topic,
|
|
33
62
|
Commit.URL: self.gitiles.url() + '/' + repo + '/+/' + commit['commit']
|
|
34
63
|
}]
|
|
35
64
|
|
|
36
65
|
def _ahead(self, commit1, commit2):
|
|
37
|
-
|
|
38
|
-
|
|
66
|
+
def _helper(data):
|
|
67
|
+
for item in data:
|
|
68
|
+
if '\u4e00' <= item <= '\u9fff':
|
|
69
|
+
return True
|
|
70
|
+
return False
|
|
71
|
+
|
|
72
|
+
buf1 = " ".join(commit1['committer']['time'].split(" ")[1:])
|
|
73
|
+
if _helper(buf1) is True:
|
|
74
|
+
buf1 = datetime.datetime.strptime(buf1, '%m月 %d %H:%M:%S %Y %z')
|
|
75
|
+
else:
|
|
76
|
+
buf1 = datetime.datetime.strptime(buf1, '%b %d %H:%M:%S %Y %z')
|
|
77
|
+
buf2 = " ".join(commit2['committer']['time'].split(" ")[1:])
|
|
78
|
+
if _helper(buf2) is True:
|
|
79
|
+
buf2 = datetime.datetime.strptime(buf2, '%m月 %d %H:%M:%S %Y %z')
|
|
80
|
+
else:
|
|
81
|
+
buf2 = datetime.datetime.strptime(buf2, '%b %d %H:%M:%S %Y %z')
|
|
39
82
|
return buf2 > buf1
|
|
40
83
|
|
|
41
84
|
def _commits(self, repo, commit1, commit2, backward):
|
|
@@ -50,7 +93,7 @@ class Querier(object):
|
|
|
50
93
|
completed = False
|
|
51
94
|
next = commits.get('next', None) if backward else commits.get('previous', None)
|
|
52
95
|
for item in commits['log']:
|
|
53
|
-
if item['commit'] == commit1[Repo.COMMIT]:
|
|
96
|
+
if item['commit'] == commit1[Repo.COMMIT] or item['commit'].startswith(commit1[Repo.COMMIT]):
|
|
54
97
|
completed = True
|
|
55
98
|
break
|
|
56
99
|
if (backward and self._ahead(item, commit)) \
|
|
@@ -78,45 +121,89 @@ class Querier(object):
|
|
|
78
121
|
return buf, status
|
|
79
122
|
|
|
80
123
|
def _commit1(self, repo, commit1, commit2):
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
Repo.COMMIT
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
124
|
+
# Try to get commits from commit2's history using the branch
|
|
125
|
+
commits = self.gitiles.commits(repo, commit2[Repo.BRANCH], commit2[Repo.COMMIT])
|
|
126
|
+
if commits is None or len(commits.get('log', [])) == 0:
|
|
127
|
+
# Fallback: try using commit hash directly instead of branch
|
|
128
|
+
commits = self.gitiles.commits(repo, commit2[Repo.COMMIT], commit2[Repo.COMMIT])
|
|
129
|
+
if commits is None or len(commits.get('log', [])) == 0:
|
|
130
|
+
Logger.warn('_commit1: Failed to get commits for repo: %s with both branch and commit hash' % repo)
|
|
131
|
+
return None, ''
|
|
132
|
+
|
|
133
|
+
iterations = 0
|
|
134
|
+
max_iterations = 100 # Prevent infinite loops
|
|
135
|
+
checked_commits = []
|
|
136
|
+
while iterations < max_iterations:
|
|
137
|
+
iterations += 1
|
|
138
|
+
commit = None
|
|
139
|
+
for item in commits.get('log', []):
|
|
140
|
+
checked_commits.append(item['commit'][:8]) # Store first 8 chars for logging
|
|
141
|
+
# Check if this commit exists in commit1's history by querying from commit1
|
|
142
|
+
data = self.gitiles.commits(repo, commit1[Repo.COMMIT], item['commit'])
|
|
143
|
+
if data is not None and len(data['log']) != 0:
|
|
144
|
+
commit = {
|
|
145
|
+
Repo.BRANCH: commit1[Repo.BRANCH],
|
|
146
|
+
Repo.COMMIT: item['commit']
|
|
147
|
+
}
|
|
148
|
+
break
|
|
149
|
+
if commit is not None:
|
|
150
|
+
break
|
|
151
|
+
data = commits.get('next', None)
|
|
152
|
+
if data is None:
|
|
153
|
+
Logger.warn('_commit1: No more commits to check (pagination ended) for repo: %s after %d iterations (checked: %s)' % (repo, iterations, ', '.join(checked_commits)))
|
|
154
|
+
break
|
|
155
|
+
commits = self.gitiles.commits(repo, commit2[Repo.BRANCH], data)
|
|
156
|
+
if commits is None:
|
|
157
|
+
Logger.warn('_commit1: Failed to get next page of commits for repo: %s' % repo)
|
|
104
158
|
break
|
|
105
|
-
return commit
|
|
106
159
|
|
|
107
|
-
|
|
160
|
+
if iterations >= max_iterations:
|
|
161
|
+
Logger.warn('_commit1: Reached max iterations (%d) for repo: %s' % (max_iterations, repo))
|
|
162
|
+
|
|
163
|
+
if commit is None:
|
|
164
|
+
return None, ''
|
|
165
|
+
data1 = self.gitiles.commit(repo, commit1[Repo.COMMIT])
|
|
166
|
+
data2 = self.gitiles.commit(repo, commit[Repo.COMMIT])
|
|
167
|
+
if data1 is None or data2 is None:
|
|
168
|
+
return None, ''
|
|
169
|
+
if self._ahead(data1, data2) is True:
|
|
170
|
+
label = Label.ADD_COMMIT
|
|
171
|
+
else:
|
|
172
|
+
label = Label.REMOVE_COMMIT
|
|
173
|
+
return commit, label
|
|
174
|
+
|
|
175
|
+
def _diff(self, repo, commit1, commit2):
|
|
108
176
|
buf = []
|
|
109
|
-
commit = self._commit1(repo, commit1, commit2)
|
|
177
|
+
commit, label = self._commit1(repo, commit1, commit2)
|
|
110
178
|
if commit is None:
|
|
111
|
-
|
|
179
|
+
Logger.warn('Failed to find common commit for repo: %s (commit1: %s, commit2: %s), treating as independent commits' % (repo, commit1[Repo.COMMIT], commit2[Repo.COMMIT]))
|
|
180
|
+
# Fallback: treat commit2 as an added commit and commit1 as context
|
|
181
|
+
data2 = self.gitiles.commit(repo, commit2[Repo.COMMIT])
|
|
182
|
+
if data2 is not None:
|
|
183
|
+
buf.extend(self._build(repo, commit2[Repo.BRANCH], data2, Label.ADD_COMMIT))
|
|
184
|
+
return buf
|
|
185
|
+
|
|
186
|
+
# Try with branch first, if it fails, use commit hash
|
|
112
187
|
commits, status = self._commits(repo, commit, commit2, True)
|
|
113
188
|
if status is False:
|
|
114
|
-
|
|
189
|
+
# Retry with commit hash as branch
|
|
190
|
+
commit2_with_hash_branch = {
|
|
191
|
+
Repo.BRANCH: commit2[Repo.COMMIT],
|
|
192
|
+
Repo.COMMIT: commit2[Repo.COMMIT]
|
|
193
|
+
}
|
|
194
|
+
commits, status = self._commits(repo, commit, commit2_with_hash_branch, True)
|
|
195
|
+
if status is False:
|
|
196
|
+
Logger.warn('Failed to get commits between common commit and commit2 for repo: %s (tried both branch and commit hash)' % repo)
|
|
197
|
+
return []
|
|
115
198
|
for item in commits:
|
|
116
|
-
buf.extend(self._build(repo, commit2[Repo.BRANCH], item,
|
|
199
|
+
buf.extend(self._build(repo, commit2[Repo.BRANCH], item, Label.ADD_COMMIT))
|
|
117
200
|
if commit[Repo.COMMIT] != commit1[Repo.COMMIT]:
|
|
118
|
-
|
|
201
|
+
if label == Label.ADD_COMMIT:
|
|
202
|
+
commits, status = self._commits(repo, commit1, commit, True)
|
|
203
|
+
else:
|
|
204
|
+
commits, status = self._commits(repo, commit, commit1, True)
|
|
119
205
|
if status is False:
|
|
206
|
+
Logger.warn('Failed to get commits between commit1 and common commit for repo: %s' % repo)
|
|
120
207
|
return []
|
|
121
208
|
for item in commits:
|
|
122
209
|
buf.extend(self._build(repo, commit1[Repo.BRANCH], item, label))
|
|
@@ -125,12 +212,19 @@ class Querier(object):
|
|
|
125
212
|
def _fetch(self, data, label):
|
|
126
213
|
def _helper(repo, commit, label):
|
|
127
214
|
buf1, buf2 = commit
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
return self._build(repo, buf2[Repo.BRANCH],
|
|
215
|
+
Logger.info(label + ': ' + repo)
|
|
216
|
+
if label == Label.ADD_REPO:
|
|
217
|
+
data = self.gitiles.commit(repo, buf2[Repo.COMMIT])
|
|
218
|
+
if data is None:
|
|
219
|
+
return []
|
|
220
|
+
return self._build(repo, buf2[Repo.BRANCH], data, label)
|
|
221
|
+
elif label == Label.REMOVE_REPO:
|
|
222
|
+
data = self.gitiles.commit(repo, buf1[Repo.COMMIT])
|
|
223
|
+
if data is None:
|
|
224
|
+
return []
|
|
225
|
+
return self._build(repo, buf1[Repo.BRANCH], data, label)
|
|
226
|
+
elif label == Label.UPDATE_REPO:
|
|
227
|
+
return self._diff(repo, buf1, buf2)
|
|
134
228
|
else:
|
|
135
229
|
return []
|
|
136
230
|
|
|
@@ -141,7 +235,7 @@ class Querier(object):
|
|
|
141
235
|
|
|
142
236
|
def run(self, data):
|
|
143
237
|
buf = []
|
|
144
|
-
buf.extend(self._fetch(data,
|
|
145
|
-
buf.extend(self._fetch(data,
|
|
146
|
-
buf.extend(self._fetch(data,
|
|
238
|
+
buf.extend(self._fetch(data, Label.ADD_REPO))
|
|
239
|
+
buf.extend(self._fetch(data, Label.REMOVE_REPO))
|
|
240
|
+
buf.extend(self._fetch(data, Label.UPDATE_REPO))
|
|
147
241
|
return buf
|
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: diffmanifests
|
|
3
|
+
Version: 3.4.1
|
|
4
|
+
Summary: Diff Manifests via Gitiles API
|
|
5
|
+
Home-page: https://github.com/craftslab/diffmanifests
|
|
6
|
+
Download-URL: https://github.com/craftslab/diffmanifests/archive/v3.4.1.tar.gz
|
|
7
|
+
Author: Jia Jia
|
|
8
|
+
Author-email: angersax@sina.com
|
|
9
|
+
License: Apache-2.0
|
|
10
|
+
Keywords: diff,manifests,gitiles,api
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
License-File: LICENSE
|
|
16
|
+
Requires-Dist: colorama
|
|
17
|
+
Requires-Dist: openpyxl
|
|
18
|
+
Requires-Dist: requests
|
|
19
|
+
Requires-Dist: xmltodict
|
|
20
|
+
Provides-Extra: dev
|
|
21
|
+
Requires-Dist: coverage; extra == "dev"
|
|
22
|
+
Requires-Dist: coveralls; extra == "dev"
|
|
23
|
+
Requires-Dist: pytest; extra == "dev"
|
|
24
|
+
Requires-Dist: setuptools; extra == "dev"
|
|
25
|
+
Requires-Dist: twine; extra == "dev"
|
|
26
|
+
Requires-Dist: wheel; extra == "dev"
|
|
27
|
+
Dynamic: author
|
|
28
|
+
Dynamic: author-email
|
|
29
|
+
Dynamic: classifier
|
|
30
|
+
Dynamic: description
|
|
31
|
+
Dynamic: description-content-type
|
|
32
|
+
Dynamic: download-url
|
|
33
|
+
Dynamic: home-page
|
|
34
|
+
Dynamic: keywords
|
|
35
|
+
Dynamic: license
|
|
36
|
+
Dynamic: license-file
|
|
37
|
+
Dynamic: provides-extra
|
|
38
|
+
Dynamic: requires-dist
|
|
39
|
+
Dynamic: summary
|
|
40
|
+
|
|
41
|
+
<div align="center">
|
|
42
|
+
|
|
43
|
+
# 📋 diffmanifests
|
|
44
|
+
|
|
45
|
+
**A powerful tool for deep manifest comparison via Gerrit & Gitiles API**
|
|
46
|
+
|
|
47
|
+
[English](README.md) | [简体中文](README_cn.md)
|
|
48
|
+
|
|
49
|
+
[](https://pypi.org/project/diffmanifests/)
|
|
50
|
+
[](https://coveralls.io/github/craftslab/diffmanifests?branch=master)
|
|
51
|
+
[](https://github.com/craftslab/diffmanifests/blob/master/LICENSE)
|
|
52
|
+
[](https://www.python.org/downloads/)
|
|
53
|
+
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## 🌟 Overview
|
|
59
|
+
|
|
60
|
+
**diffmanifests** is a sophisticated CLI tool designed to reveal deeper differences between manifest files by leveraging the Gerrit and Gitiles APIs. It provides comprehensive change tracking, hashtag support, and detailed commit analysis for efficient manifest version management.
|
|
61
|
+
|
|
62
|
+
### ✨ Key Highlights
|
|
63
|
+
|
|
64
|
+
- 🔍 **Deep Comparison**: Analyze differences between manifest versions with precision
|
|
65
|
+
- 🏷️ **Hashtag Integration**: Full support for Gerrit hashtags and categorization
|
|
66
|
+
- 📊 **Visual Reporting**: Generate detailed JSON reports with comprehensive commit information
|
|
67
|
+
- 🔄 **API-Powered**: Seamlessly integrates with Gerrit and Gitiles REST APIs
|
|
68
|
+
- ⚡ **Easy to Use**: Simple command-line interface with clear configuration
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## 📋 Table of Contents
|
|
73
|
+
|
|
74
|
+
- [Requirements](#-requirements)
|
|
75
|
+
- [Installation](#-installation)
|
|
76
|
+
- [Quick Start](#-quick-start)
|
|
77
|
+
- [Configuration](#-configuration)
|
|
78
|
+
- [Features](#-features)
|
|
79
|
+
- [Output Format](#-output-format)
|
|
80
|
+
- [Examples](#-examples)
|
|
81
|
+
- [Development](#-development)
|
|
82
|
+
- [License](#-license)
|
|
83
|
+
- [References](#-references)
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## 🔧 Requirements
|
|
88
|
+
|
|
89
|
+
- **Python**: >= 3.7
|
|
90
|
+
- **Dependencies**:
|
|
91
|
+
- `colorama` - Terminal color output
|
|
92
|
+
- `openpyxl` - Excel file handling
|
|
93
|
+
- `requests` - HTTP library
|
|
94
|
+
- `xmltodict` - XML parsing
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## 📦 Installation
|
|
99
|
+
|
|
100
|
+
### Install from PyPI
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
pip install diffmanifests
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Upgrade to Latest Version
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
pip install diffmanifests --upgrade
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Install from Source
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
git clone https://github.com/craftslab/diffmanifests.git
|
|
116
|
+
cd diffmanifests
|
|
117
|
+
pip install -e .
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## 🚀 Quick Start
|
|
123
|
+
|
|
124
|
+
### Basic Usage
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
diffmanifests \
|
|
128
|
+
--config-file config.json \
|
|
129
|
+
--manifest1-file manifest1.xml \
|
|
130
|
+
--manifest2-file manifest2.xml \
|
|
131
|
+
--output-file output.json
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Command Line Arguments
|
|
135
|
+
|
|
136
|
+
| Argument | Description | Required |
|
|
137
|
+
|----------|-------------|----------|
|
|
138
|
+
| `--config-file` | Path to configuration JSON file | ✅ |
|
|
139
|
+
| `--manifest1-file` | Path to first manifest XML file (older version) | ✅ |
|
|
140
|
+
| `--manifest2-file` | Path to second manifest XML file (newer version) | ✅ |
|
|
141
|
+
| `--output-file` | Path to output JSON file for results | ✅ |
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## ⚙️ Configuration
|
|
146
|
+
|
|
147
|
+
Configuration parameters can be set in a JSON file. See the [config directory](https://github.com/craftslab/diffmanifests/blob/master/diffmanifests/config) for examples.
|
|
148
|
+
|
|
149
|
+
### Configuration Structure
|
|
150
|
+
|
|
151
|
+
Create a `config.json` file with the following structure:
|
|
152
|
+
|
|
153
|
+
```json
|
|
154
|
+
{
|
|
155
|
+
"gerrit": {
|
|
156
|
+
"url": "https://your-gerrit-instance.com",
|
|
157
|
+
"user": "your-username",
|
|
158
|
+
"pass": "your-password-or-token"
|
|
159
|
+
},
|
|
160
|
+
"gitiles": {
|
|
161
|
+
"url": "https://your-gitiles-instance.com",
|
|
162
|
+
"user": "your-username",
|
|
163
|
+
"pass": "your-password-or-token",
|
|
164
|
+
"retry": 3,
|
|
165
|
+
"timeout": 30
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Configuration Parameters
|
|
171
|
+
|
|
172
|
+
#### Gerrit Settings
|
|
173
|
+
|
|
174
|
+
| Parameter | Type | Description |
|
|
175
|
+
|-----------|------|-------------|
|
|
176
|
+
| `url` | string | Gerrit instance URL |
|
|
177
|
+
| `user` | string | Authentication username |
|
|
178
|
+
| `pass` | string | Authentication password or API token |
|
|
179
|
+
|
|
180
|
+
#### Gitiles Settings
|
|
181
|
+
|
|
182
|
+
| Parameter | Type | Description | Default |
|
|
183
|
+
|-----------|------|-------------|---------|
|
|
184
|
+
| `url` | string | Gitiles instance URL | - |
|
|
185
|
+
| `user` | string | Authentication username | - |
|
|
186
|
+
| `pass` | string | Authentication password or API token | - |
|
|
187
|
+
| `retry` | integer | Number of retry attempts for failed requests | 1 |
|
|
188
|
+
| `timeout` | integer | Request timeout in seconds (-1 for no timeout) | -1 |
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## 🎯 Features
|
|
193
|
+
|
|
194
|
+
### 📊 Manifest Comparison
|
|
195
|
+
|
|
196
|
+
Compare two manifest versions to identify changes between commits. The tool analyzes differences using a three-way comparison model:
|
|
197
|
+
|
|
198
|
+

|
|
199
|
+
|
|
200
|
+
**Comparison Logic**:
|
|
201
|
+
- **Diagram A**: Changes from commit 1 to commit 2
|
|
202
|
+
- **Diagram B**: Alternative change paths
|
|
203
|
+
- **Diagram C**: Merge scenarios
|
|
204
|
+
|
|
205
|
+
### 🏷️ Hashtag Support
|
|
206
|
+
|
|
207
|
+
Comprehensive support for Gerrit hashtags through REST API v3.12.1, enabling better change tracking and categorization.
|
|
208
|
+
|
|
209
|
+
#### Key Benefits
|
|
210
|
+
|
|
211
|
+
✅ **Automatic hashtag extraction** from Gerrit changes
|
|
212
|
+
✅ **Enhanced categorization** and filtering capabilities
|
|
213
|
+
✅ **Seamless Gerrit workflow** integration
|
|
214
|
+
✅ **Graceful fallback** for changes without hashtags
|
|
215
|
+
|
|
216
|
+
#### Use Cases
|
|
217
|
+
|
|
218
|
+
| Hashtags | Use Case |
|
|
219
|
+
|----------|----------|
|
|
220
|
+
| `["feature", "ui", "enhancement"]` | New UI features |
|
|
221
|
+
| `["bugfix", "critical"]` | Critical bug fixes |
|
|
222
|
+
| `["security", "cve"]` | Security-related changes |
|
|
223
|
+
| `["refactor", "cleanup"]` | Code refactoring |
|
|
224
|
+
| `[]` | Changes without hashtags |
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## 📄 Output Format
|
|
229
|
+
|
|
230
|
+
The tool generates a JSON file with detailed information about each changed commit.
|
|
231
|
+
|
|
232
|
+
### Output Structure
|
|
233
|
+
|
|
234
|
+
```json
|
|
235
|
+
{
|
|
236
|
+
"author": "Developer Name <dev@example.com>",
|
|
237
|
+
"branch": "master",
|
|
238
|
+
"change": "https://gerrit.example.com/c/12345",
|
|
239
|
+
"commit": "abc123def456789...",
|
|
240
|
+
"committer": "Developer Name <dev@example.com>",
|
|
241
|
+
"date": "2025-08-20 12:00:00 +0000",
|
|
242
|
+
"diff": "ADD COMMIT",
|
|
243
|
+
"hashtags": ["security", "cve", "bugfix"],
|
|
244
|
+
"message": "Fix security vulnerability CVE-2025-1234",
|
|
245
|
+
"repo": "platform/frameworks/base",
|
|
246
|
+
"topic": "security-fix",
|
|
247
|
+
"url": "https://android.googlesource.com/platform/frameworks/base/+/abc123def456789"
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Output Fields
|
|
252
|
+
|
|
253
|
+
| Field | Type | Description |
|
|
254
|
+
|-------|------|-------------|
|
|
255
|
+
| `author` | string | Original commit author |
|
|
256
|
+
| `branch` | string | Target branch name |
|
|
257
|
+
| `change` | string | Gerrit change URL |
|
|
258
|
+
| `commit` | string | Git commit SHA |
|
|
259
|
+
| `committer` | string | Person who committed the change |
|
|
260
|
+
| `date` | string | Commit timestamp |
|
|
261
|
+
| `diff` | string | Type of change (ADD COMMIT, REMOVE COMMIT, etc.) |
|
|
262
|
+
| `hashtags` | array | List of associated hashtags |
|
|
263
|
+
| `message` | string | Commit message |
|
|
264
|
+
| `repo` | string | Repository path |
|
|
265
|
+
| `topic` | string | Gerrit topic name |
|
|
266
|
+
| `url` | string | Gitiles commit URL |
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## 💡 Examples
|
|
271
|
+
|
|
272
|
+
### Example 1: Basic Comparison
|
|
273
|
+
|
|
274
|
+
```bash
|
|
275
|
+
diffmanifests \
|
|
276
|
+
--config-file ./config/config.json \
|
|
277
|
+
--manifest1-file ./data/android-11.xml \
|
|
278
|
+
--manifest2-file ./data/android-12.xml \
|
|
279
|
+
--output-file ./results/diff-output.json
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### Example 2: With Custom Configuration
|
|
283
|
+
|
|
284
|
+
```bash
|
|
285
|
+
# config.json
|
|
286
|
+
{
|
|
287
|
+
"gerrit": {
|
|
288
|
+
"url": "https://android-review.googlesource.com",
|
|
289
|
+
"user": "developer",
|
|
290
|
+
"pass": "your-token"
|
|
291
|
+
},
|
|
292
|
+
"gitiles": {
|
|
293
|
+
"url": "https://android.googlesource.com",
|
|
294
|
+
"user": "developer",
|
|
295
|
+
"pass": "your-token",
|
|
296
|
+
"retry": 5,
|
|
297
|
+
"timeout": 60
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
# Run comparison
|
|
302
|
+
diffmanifests \
|
|
303
|
+
--config-file config.json \
|
|
304
|
+
--manifest1-file old-manifest.xml \
|
|
305
|
+
--manifest2-file new-manifest.xml \
|
|
306
|
+
--output-file changes.json
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### Example 3: Analyzing Output
|
|
310
|
+
|
|
311
|
+
```python
|
|
312
|
+
import json
|
|
313
|
+
|
|
314
|
+
# Load the output
|
|
315
|
+
with open('output.json', 'r') as f:
|
|
316
|
+
changes = json.load(f)
|
|
317
|
+
|
|
318
|
+
# Filter security-related changes
|
|
319
|
+
security_changes = [
|
|
320
|
+
c for c in changes
|
|
321
|
+
if 'security' in c.get('hashtags', []) or 'cve' in c.get('hashtags', [])
|
|
322
|
+
]
|
|
323
|
+
|
|
324
|
+
print(f"Found {len(security_changes)} security-related changes")
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
---
|
|
328
|
+
|
|
329
|
+
## 🛠️ Development
|
|
330
|
+
|
|
331
|
+
### Setting Up Development Environment
|
|
332
|
+
|
|
333
|
+
```bash
|
|
334
|
+
# Clone the repository
|
|
335
|
+
git clone https://github.com/craftslab/diffmanifests.git
|
|
336
|
+
cd diffmanifests
|
|
337
|
+
|
|
338
|
+
# Install development dependencies
|
|
339
|
+
pip install -e .[dev]
|
|
340
|
+
|
|
341
|
+
# Run tests
|
|
342
|
+
pytest tests/
|
|
343
|
+
|
|
344
|
+
# Run tests with coverage
|
|
345
|
+
coverage run -m pytest tests/
|
|
346
|
+
coverage report
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### Running Tests
|
|
350
|
+
|
|
351
|
+
```bash
|
|
352
|
+
# Run all tests
|
|
353
|
+
pytest
|
|
354
|
+
|
|
355
|
+
# Run specific test module
|
|
356
|
+
pytest tests/differ/test_differ.py
|
|
357
|
+
|
|
358
|
+
# Run with verbose output
|
|
359
|
+
pytest -v
|
|
360
|
+
|
|
361
|
+
# Run with coverage report
|
|
362
|
+
pytest --cov=diffmanifests tests/
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
### Project Scripts
|
|
366
|
+
|
|
367
|
+
Located in the `script/` directory:
|
|
368
|
+
|
|
369
|
+
- `clean.sh` - Clean build artifacts and cache files
|
|
370
|
+
- `dist.sh` - Build distribution packages
|
|
371
|
+
- `install.sh` - Install the package locally
|
|
372
|
+
- `run.sh` - Run the tool with test data
|
|
373
|
+
- `test.sh` - Execute test suite
|
|
374
|
+
|
|
375
|
+
---
|
|
376
|
+
|
|
377
|
+
## 📜 License
|
|
378
|
+
|
|
379
|
+
This project is licensed under the **Apache License 2.0**.
|
|
380
|
+
|
|
381
|
+
See [LICENSE](https://github.com/craftslab/diffmanifests/blob/master/LICENSE) for full details.
|
|
382
|
+
|
|
383
|
+
---
|
|
384
|
+
|
|
385
|
+
## 📚 References
|
|
386
|
+
|
|
387
|
+
- [Gerrit REST API Documentation](https://gerrit-documentation.storage.googleapis.com/Documentation/3.12.1/rest-api.html)
|
|
388
|
+
- [Gerrit ChangeInfo Entity](https://gerrit-documentation.storage.googleapis.com/Documentation/3.12.1/rest-api-changes.html#change-info)
|
|
389
|
+
- [git-repo/subcmds/diffmanifests](https://gerrit.googlesource.com/git-repo/+/master/subcmds/diffmanifests.py)
|
|
390
|
+
- [Gitiles API Documentation](https://gerrit.googlesource.com/gitiles/+/master/Documentation/design.md)
|
|
391
|
+
|
|
392
|
+
---
|
|
393
|
+
|
|
394
|
+
## 🤝 Contributing
|
|
395
|
+
|
|
396
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
397
|
+
|
|
398
|
+
### How to Contribute
|
|
399
|
+
|
|
400
|
+
1. Fork the repository
|
|
401
|
+
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
|
402
|
+
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
|
|
403
|
+
4. Push to the branch (`git push origin feature/amazing-feature`)
|
|
404
|
+
5. Open a Pull Request
|
|
405
|
+
|
|
406
|
+
---
|
|
407
|
+
|
|
408
|
+
## 📞 Support
|
|
409
|
+
|
|
410
|
+
- **Issues**: [GitHub Issues](https://github.com/craftslab/diffmanifests/issues)
|
|
411
|
+
- **Email**: angersax@sina.com
|
|
412
|
+
- **PyPI**: [diffmanifests on PyPI](https://pypi.org/project/diffmanifests/)
|
|
413
|
+
|
|
414
|
+
---
|
|
415
|
+
|
|
416
|
+
<div align="center">
|
|
417
|
+
|
|
418
|
+
**Made with ❤️ by [craftslab](https://github.com/craftslab)**
|
|
419
|
+
|
|
420
|
+
⭐ Star this repository if you find it helpful!
|
|
421
|
+
|
|
422
|
+
</div>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
diffmanifests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
diffmanifests/main.py,sha256=SI14T_8yTuzC1KOQL-QpXwPKlALr1DyI1KBM1vIhFtI,2179
|
|
3
|
+
diffmanifests/cmd/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
diffmanifests/cmd/argument.py,sha256=vP7Wlczk1KFGtubNrLRrp3B6imuOexkeAzb8AzbzOGM,1736
|
|
5
|
+
diffmanifests/cmd/banner.py,sha256=kpqxOlU4_wJxOswV_iX6eevNppSbn08O-aII-AjEAFQ,297
|
|
6
|
+
diffmanifests/cmd/version.py,sha256=KfXzpS9UAWz0HpsyyAXf_724jo3vwxo0c8yf10eYkIM,43
|
|
7
|
+
diffmanifests/config/config.json,sha256=xYVNdl-xZAs_VzvB_vzgQF3zaMZq9Zam1ncCXOPxKfM,299
|
|
8
|
+
diffmanifests/differ/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
+
diffmanifests/differ/differ.py,sha256=nIfwogtTMeTlofoaiX-wYRKHYx4m3T7LwKbRlMZqpdc,3436
|
|
10
|
+
diffmanifests/gerrit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
+
diffmanifests/gerrit/gerrit.py,sha256=Q-ZmgVumqCyz1e4tYaTLc7T7qgtGjQJdIXER_GImPwo,1749
|
|
12
|
+
diffmanifests/gitiles/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
|
+
diffmanifests/gitiles/gitiles.py,sha256=0I1QuqIZX-I7obdbixvKr5C3bNNbMDdRc2dz7JJhet4,2616
|
|
14
|
+
diffmanifests/logger/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
|
+
diffmanifests/logger/logger.py,sha256=uxofaUpOd2IzEoVImNMztDzqk_F939b7FcePJ6_Vbzw,1404
|
|
16
|
+
diffmanifests/printer/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
|
+
diffmanifests/printer/printer.py,sha256=Fbqy6wqxiIgOPnVESzXHDFTslTZpfrvESdpPLxWe-kM,3467
|
|
18
|
+
diffmanifests/proto/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
19
|
+
diffmanifests/proto/proto.py,sha256=sQ808ExLEA_u7eQkYrKkeFkAn3jPEgujz69S52FiyfQ,1301
|
|
20
|
+
diffmanifests/querier/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
21
|
+
diffmanifests/querier/querier.py,sha256=5Vvkn2M1dSxNadHZYDXXICe1LNYLyqJi_ymJrK35iQc,10236
|
|
22
|
+
diffmanifests-3.4.1.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
23
|
+
diffmanifests-3.4.1.dist-info/METADATA,sha256=Lm9Ln1HOnJrXMctxT25yjEVVROq-VUL_TrbrgKnvbHY,11159
|
|
24
|
+
diffmanifests-3.4.1.dist-info/WHEEL,sha256=JNWh1Fm1UdwIQV075glCn4MVuCRs0sotJIq-J6rbxCU,109
|
|
25
|
+
diffmanifests-3.4.1.dist-info/entry_points.txt,sha256=l9pmGB0o1VRTFRza8MFEI6_PcOpBJV1BCbdunD7I4kY,58
|
|
26
|
+
diffmanifests-3.4.1.dist-info/top_level.txt,sha256=EHlzZzRVketGFo3Qhjb1pGSfn2FjXZOLmjuTsCqIHlA,14
|
|
27
|
+
diffmanifests-3.4.1.dist-info/RECORD,,
|
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: diffmanifests
|
|
3
|
-
Version: 1.2.0
|
|
4
|
-
Summary: Diff Manifests via Gitiles API
|
|
5
|
-
Home-page: https://github.com/craftslab/diffmanifests
|
|
6
|
-
Author: Jia Jia
|
|
7
|
-
Author-email: angersax@sina.com
|
|
8
|
-
License: Apache-2.0
|
|
9
|
-
Download-URL: https://github.com/craftslab/diffmanifests/archive/v1.2.0.tar.gz
|
|
10
|
-
Keywords: diff,manifests,gitiles,api
|
|
11
|
-
Platform: UNKNOWN
|
|
12
|
-
Classifier: Programming Language :: Python :: 3
|
|
13
|
-
Classifier: License :: OSI Approved :: Apache Software License
|
|
14
|
-
Classifier: Operating System :: OS Independent
|
|
15
|
-
Description-Content-Type: text/markdown
|
|
16
|
-
Requires-Dist: colorama
|
|
17
|
-
Requires-Dist: coverage
|
|
18
|
-
Requires-Dist: coveralls
|
|
19
|
-
Requires-Dist: openpyxl
|
|
20
|
-
Requires-Dist: pytest
|
|
21
|
-
Requires-Dist: requests
|
|
22
|
-
Requires-Dist: setuptools
|
|
23
|
-
Requires-Dist: twine
|
|
24
|
-
Requires-Dist: wheel
|
|
25
|
-
Requires-Dist: xmltodict
|
|
26
|
-
|
|
27
|
-
# Diff Manifests
|
|
28
|
-
|
|
29
|
-
[](https://pypi.org/project/diffmanifests/)
|
|
30
|
-
[](https://travis-ci.com/craftslab/diffmanifests)
|
|
31
|
-
[](https://coveralls.io/github/craftslab/diffmanifests?branch=master)
|
|
32
|
-
[](https://github.com/craftslab/diffmanifests/blob/master/LICENSE)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
*Diff Manifests* is a tool used to see deeper differences between manifests via Gitiles API.
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
## Requirement
|
|
41
|
-
|
|
42
|
-
- python (3.7+)
|
|
43
|
-
- pip
|
|
44
|
-
- python-dev
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
## Installation
|
|
49
|
-
|
|
50
|
-
On Ubuntu / Mint, install *Diff Manifests* with the following commands:
|
|
51
|
-
|
|
52
|
-
```bash
|
|
53
|
-
apt update
|
|
54
|
-
apt install python3-dev python3-pip python3-setuptools
|
|
55
|
-
pip install diffmanifests
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
On OS X, install *Diff Manifests* via [Homebrew](https://brew.sh/) (or via [Linuxbrew](https://linuxbrew.sh/) on Linux):
|
|
59
|
-
|
|
60
|
-
```
|
|
61
|
-
TBD
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
On Windows, install *Diff Manifests* with the following commands:
|
|
65
|
-
|
|
66
|
-
```
|
|
67
|
-
pip install -U pywin32
|
|
68
|
-
pip install -U pyinstaller
|
|
69
|
-
pip install -Ur requirements.txt
|
|
70
|
-
|
|
71
|
-
pyinstaller --clean --name diffmanifests -F diff.py
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
## Updating
|
|
77
|
-
|
|
78
|
-
```bash
|
|
79
|
-
pip install diffmanifests --upgrade
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
## Running
|
|
85
|
-
|
|
86
|
-
```bash
|
|
87
|
-
diffmanifests \
|
|
88
|
-
--config-file config.json \
|
|
89
|
-
--manifest1-file manifest1.xml \
|
|
90
|
-
--manifest2-file manifest2.xml \
|
|
91
|
-
--output-file output.json
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
## Setting
|
|
97
|
-
|
|
98
|
-
*Diff Manifests* parameters can be set in the directory [config](https://github.com/craftslab/diffmanifests/blob/master/diffmanifests/config).
|
|
99
|
-
|
|
100
|
-
An example of configuration in [config.json](https://github.com/craftslab/diffmanifests/blob/master/diffmanifests/config/config.json):
|
|
101
|
-
|
|
102
|
-
```
|
|
103
|
-
{
|
|
104
|
-
"gitiles": {
|
|
105
|
-
"host": "localhost",
|
|
106
|
-
"pass": "pass",
|
|
107
|
-
"port": 80,
|
|
108
|
-
"user": "user"
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
## Feature
|
|
116
|
-
|
|
117
|
-
*Diff Manifests* supports to compare commit 2 with commit 1 in diagram A & B, but diagram C not supported.
|
|
118
|
-
|
|
119
|
-

|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
## License
|
|
124
|
-
|
|
125
|
-
Project License can be found [here](https://github.com/craftslab/diffmanifests/blob/master/LICENSE).
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
## Reference
|
|
130
|
-
|
|
131
|
-
[git-repo/subcmds/diffmanifests](https://gerrit.googlesource.com/git-repo/+/master/subcmds/diffmanifests.py)
|
|
132
|
-
|
|
133
|
-
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
diffmanifests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
diffmanifests/main.py,sha256=SI14T_8yTuzC1KOQL-QpXwPKlALr1DyI1KBM1vIhFtI,2179
|
|
3
|
-
diffmanifests/cmd/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
-
diffmanifests/cmd/argument.py,sha256=GDtvCOGFlEP_A_pA9CpToySFOJzxxpc6CEVdWhWPydo,1825
|
|
5
|
-
diffmanifests/cmd/banner.py,sha256=kpqxOlU4_wJxOswV_iX6eevNppSbn08O-aII-AjEAFQ,297
|
|
6
|
-
diffmanifests/cmd/version.py,sha256=YHEpMV7IXyFbjF2MUc8Yrw8J55rZnR5-0ddI5UmVtEA,43
|
|
7
|
-
diffmanifests/config/config.json,sha256=WLo0iaIdPZuzrptyg2u1OACgReI5-EXMo8dWGa-Kmjo,101
|
|
8
|
-
diffmanifests/differ/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
-
diffmanifests/differ/differ.py,sha256=CHO9-H67tWK4s-b0cYu7Psbw4tUQldHm6AKsjzFPcRA,3371
|
|
10
|
-
diffmanifests/gitiles/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
-
diffmanifests/gitiles/gitiles.py,sha256=xFLgebDsgSgMsfSBXQ7m_ZSMdonT0NVt6mZbVSkh46Q,1690
|
|
12
|
-
diffmanifests/logger/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
|
-
diffmanifests/logger/logger.py,sha256=uxofaUpOd2IzEoVImNMztDzqk_F939b7FcePJ6_Vbzw,1404
|
|
14
|
-
diffmanifests/printer/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
|
-
diffmanifests/printer/printer.py,sha256=9te-HMbdsZKxaz6eirp5zZGuZXKwp8AbFemPsDT7qK0,2260
|
|
16
|
-
diffmanifests/proto/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
|
-
diffmanifests/proto/proto.py,sha256=UF3etfLiBUPFnc98Lj6d_mGv5pIrsaDb_e39LyxK2Ng,926
|
|
18
|
-
diffmanifests/querier/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
19
|
-
diffmanifests/querier/querier.py,sha256=0ANYQ84G3AWixrhP1NfAh5rrq1BRine55ym8RjaJhxs,5327
|
|
20
|
-
diffmanifests-1.2.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
21
|
-
diffmanifests-1.2.0.dist-info/METADATA,sha256=2zGYDNCmNHd7EQSgj5Lj43Pv4BojmTd8clrtb3F-EqI,3106
|
|
22
|
-
diffmanifests-1.2.0.dist-info/WHEEL,sha256=kGT74LWyRUZrL4VgLh6_g12IeVl_9u9ZVhadrgXZUEY,110
|
|
23
|
-
diffmanifests-1.2.0.dist-info/entry_points.txt,sha256=aQ6lEOwzCC0D3pI-fKkZQAFBd76q_KQCt9b8kzra_RE,59
|
|
24
|
-
diffmanifests-1.2.0.dist-info/top_level.txt,sha256=EHlzZzRVketGFo3Qhjb1pGSfn2FjXZOLmjuTsCqIHlA,14
|
|
25
|
-
diffmanifests-1.2.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|