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.

@@ -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):
@@ -1,3 +1,3 @@
1
1
  # -*- coding: utf-8 -*-
2
2
 
3
- VERSION = '1.2.0'
3
+ VERSION = '3.4.1'
@@ -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
  }
@@ -1,6 +1,6 @@
1
1
  # -*- coding: utf-8 -*-
2
2
 
3
- from ..proto.proto import Diff, Repo
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
- changed = {}
53
- buf = list(set(name1).intersection(set(name2)))
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
- changed[item] = [
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
- deleted = {}
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
- deleted[item] = [
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
- inserted = {}
81
- buf = list(set(name2).difference(set(name1)))
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
- inserted[item] = [
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 changed, deleted, inserted
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
- changed, deleted, inserted = self._diff(data1, data2)
109
+ added, removed, updated = self._diff(data1, data2)
108
110
 
109
111
  return {
110
- Diff.CHANGE: changed,
111
- Diff.DELETE: deleted,
112
- Diff.INSERT: inserted
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
@@ -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 = requests.get(url=self._url + '/%s/+/%s?format=JSON' % (repo, commit))
36
+ response = session.get(url=self._url + '/%s/+/%s?format=JSON' % (repo, commit), timeout=self._timeout)
27
37
  else:
28
- response = requests.get(url=self._url + '/%s/+/%s?format=JSON' % (repo, commit),
29
- auth=(self._user, self._pass))
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
- return json.loads(response.text.replace(")]}'", ''))
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 = requests.get(url=self._url + '/%s/+log/%s/?s=%s&format=JSON' % (repo, branch, commit))
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 = requests.get(url=self._url + '/%s/+log/%s/?s=%s&format=JSON' % (repo, branch, commit),
39
- auth=(self._user, self._pass))
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
- return json.loads(response.text.replace(")]}'", ''))
60
+ ret = json.loads(response.text.replace(")]}'", ''))
61
+ return ret
43
62
 
44
63
  def url(self):
45
64
  return self._url
@@ -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' % (' '*3, head['A'], data[head['A']]))
50
- out.write(u'%s%s: %s\n' % (' '*3, head['B'], data[head['B']]))
51
- out.write(u'%s%s: %s\n' % (' '*1, head['C'], data[head['C']]))
52
- out.write(u'%s%s: %s\n' % (' '*1, head['D'], data[head['D']]))
53
- out.write(u'%s%s: %s\n' % (' '*3, head['E'], data[head['E']]))
54
- out.write(u'%s%s: %s\n' % (' '*1, head['F'], data[head['F']]))
55
- out.write(u'%s%s: %s\n' % (' '*0, head['G'], data[head['G']]))
56
- out.write(u'%s%s: %s\n' % (' '*0, head['H'], data[head['H']]))
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
- ws.append([item[head[key]] for key in sorted(head.keys())])
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):
@@ -3,33 +3,37 @@
3
3
 
4
4
  """Prototype
5
5
  {
6
- "change": {
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
- "delete": {
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
- "insert": {
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 Diff:
52
- CHANGE = 'change'
53
- DELETE = 'delete'
54
- INSERT = 'insert'
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'
@@ -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 ..proto.proto import Commit, Diff, Repo
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.MESSAGE: commit['message'].split('\n')[0],
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
- buf1 = datetime.datetime.strptime(commit1['committer']['time'], '%a %b %d %H:%M:%S %Y %z')
38
- buf2 = datetime.datetime.strptime(commit2['committer']['time'], '%a %b %d %H:%M:%S %Y %z')
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
- if commit1[Repo.BRANCH] == commit2[Repo.BRANCH]:
82
- return commit1
83
- commit = {
84
- Repo.BRANCH: commit2[Repo.BRANCH],
85
- Repo.COMMIT: commit1[Repo.COMMIT]
86
- }
87
- commits2, status = self._commits(repo, commit, commit2, True)
88
- buf2 = []
89
- if status is True:
90
- buf2.append(commit[Repo.COMMIT])
91
- buf2.extend([item['commit'] for item in commits2])
92
- commit = {
93
- Repo.BRANCH: commit1[Repo.BRANCH],
94
- Repo.COMMIT: commit2[Repo.COMMIT]
95
- }
96
- commits1, _ = self._commits(repo, commit, commit1, False)
97
- commit = None
98
- for item in commits1:
99
- if item['commit'] in buf2:
100
- commit = {
101
- Repo.BRANCH: commit1[Repo.BRANCH],
102
- Repo.COMMIT: item['commit']
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
- def _diff(self, repo, commit1, commit2, label):
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
- return []
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
- return []
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, label))
199
+ buf.extend(self._build(repo, commit2[Repo.BRANCH], item, Label.ADD_COMMIT))
117
200
  if commit[Repo.COMMIT] != commit1[Repo.COMMIT]:
118
- commits, status = self._commits(repo, commit1, commit, True)
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
- if label == Diff.CHANGE:
129
- return self._diff(repo, buf1, buf2, label)
130
- elif label == Diff.DELETE:
131
- return self._build(repo, buf1[Repo.BRANCH], self.gitiles.commit(repo, buf1[Repo.COMMIT]), label)
132
- elif label == Diff.INSERT:
133
- return self._build(repo, buf2[Repo.BRANCH], self.gitiles.commit(repo, buf2[Repo.COMMIT]), label)
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, Diff.CHANGE))
145
- buf.extend(self._fetch(data, Diff.DELETE))
146
- buf.extend(self._fetch(data, Diff.INSERT))
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
+ [![PyPI](https://img.shields.io/pypi/v/diffmanifests.svg?color=brightgreen)](https://pypi.org/project/diffmanifests/)
50
+ [![Coverage Status](https://coveralls.io/repos/github/craftslab/diffmanifests/badge.svg?branch=master)](https://coveralls.io/github/craftslab/diffmanifests?branch=master)
51
+ [![License](https://img.shields.io/github/license/craftslab/diffmanifests.svg?color=brightgreen)](https://github.com/craftslab/diffmanifests/blob/master/LICENSE)
52
+ [![Python Version](https://img.shields.io/badge/python-3.7+-blue.svg)](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
+ ![branch](branch.png)
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,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.34.2)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py2-none-any
5
5
  Tag: py3-none-any
@@ -1,3 +1,2 @@
1
1
  [console_scripts]
2
2
  diffmanifests = diffmanifests.main:main
3
-
@@ -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
- [![PyPI](https://img.shields.io/pypi/v/diffmanifests.svg?color=brightgreen)](https://pypi.org/project/diffmanifests/)
30
- [![Travis](https://travis-ci.com/craftslab/diffmanifests.svg?branch=master)](https://travis-ci.com/craftslab/diffmanifests)
31
- [![Coverage](https://coveralls.io/repos/github/craftslab/diffmanifests/badge.svg?branch=master)](https://coveralls.io/github/craftslab/diffmanifests?branch=master)
32
- [![License](https://img.shields.io/github/license/craftslab/diffmanifests.svg?color=brightgreen)](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
- ![branch](branch.png)
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,,