git-gerrit 3.0.0__tar.gz → 3.2.0__tar.gz
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.
- {git_gerrit-3.0.0/git_gerrit.egg-info → git_gerrit-3.2.0}/PKG-INFO +36 -11
- {git_gerrit-3.0.0 → git_gerrit-3.2.0}/README.rst +35 -10
- git_gerrit-3.2.0/git_gerrit/__version__.py +1 -0
- {git_gerrit-3.0.0 → git_gerrit-3.2.0}/git_gerrit/core.py +16 -3
- {git_gerrit-3.0.0 → git_gerrit-3.2.0}/git_gerrit/db.py +188 -45
- {git_gerrit-3.0.0 → git_gerrit-3.2.0}/git_gerrit/git.py +6 -3
- {git_gerrit-3.0.0 → git_gerrit-3.2.0/git_gerrit.egg-info}/PKG-INFO +36 -11
- {git_gerrit-3.0.0 → git_gerrit-3.2.0}/tests/test_core.py +33 -2
- git_gerrit-3.2.0/tests/test_db.py +205 -0
- {git_gerrit-3.0.0 → git_gerrit-3.2.0}/tests/test_git.py +3 -1
- git_gerrit-3.0.0/git_gerrit/__version__.py +0 -1
- git_gerrit-3.0.0/tests/test_db.py +0 -121
- {git_gerrit-3.0.0 → git_gerrit-3.2.0}/LICENSE.txt +0 -0
- {git_gerrit-3.0.0 → git_gerrit-3.2.0}/MANIFEST.in +0 -0
- {git_gerrit-3.0.0 → git_gerrit-3.2.0}/git_gerrit/__init__.py +0 -0
- {git_gerrit-3.0.0 → git_gerrit-3.2.0}/git_gerrit/cli.py +0 -0
- {git_gerrit-3.0.0 → git_gerrit-3.2.0}/git_gerrit/error.py +0 -0
- {git_gerrit-3.0.0 → git_gerrit-3.2.0}/git_gerrit/spinner.py +0 -0
- {git_gerrit-3.0.0 → git_gerrit-3.2.0}/git_gerrit.egg-info/SOURCES.txt +0 -0
- {git_gerrit-3.0.0 → git_gerrit-3.2.0}/git_gerrit.egg-info/dependency_links.txt +0 -0
- {git_gerrit-3.0.0 → git_gerrit-3.2.0}/git_gerrit.egg-info/entry_points.txt +0 -0
- {git_gerrit-3.0.0 → git_gerrit-3.2.0}/git_gerrit.egg-info/requires.txt +0 -0
- {git_gerrit-3.0.0 → git_gerrit-3.2.0}/git_gerrit.egg-info/top_level.txt +0 -0
- {git_gerrit-3.0.0 → git_gerrit-3.2.0}/pyproject.toml +0 -0
- {git_gerrit-3.0.0 → git_gerrit-3.2.0}/setup.cfg +0 -0
- {git_gerrit-3.0.0 → git_gerrit-3.2.0}/setup.py +0 -0
- {git_gerrit-3.0.0 → git_gerrit-3.2.0}/tests/test_cli.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: git_gerrit
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.2.0
|
|
4
4
|
Summary: Gerrit review system command line tools.
|
|
5
5
|
Home-page: https://github.com/meffie/git-gerrit
|
|
6
6
|
Author: Michael Meffie
|
|
@@ -39,7 +39,7 @@ with the **pygerrit2** package to access the Gerrit REST API.
|
|
|
39
39
|
Commands
|
|
40
40
|
========
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
**git-gerrit** commands::
|
|
43
43
|
|
|
44
44
|
git gerrit-checkout Fetch then checkout by gerrit number.
|
|
45
45
|
git gerrit-cherry-pick Cherry pick from upstream branch by gerrit number to make a new gerrit.
|
|
@@ -57,16 +57,32 @@ Installation
|
|
|
57
57
|
============
|
|
58
58
|
|
|
59
59
|
.. _pipx: https://pipx.pypa.io/stable/
|
|
60
|
+
.. _uv: https://github.com/astral-sh/uv
|
|
61
|
+
|
|
62
|
+
The recommended way to install **git-gerrit** is with `uv`_ or `pipx`_.
|
|
63
|
+
|
|
64
|
+
To install with ``uv``, run the following as a regular user:
|
|
60
65
|
|
|
61
|
-
|
|
66
|
+
.. code-block:: console
|
|
67
|
+
|
|
68
|
+
uv tool install git-gerrit
|
|
69
|
+
uv tool update-shell
|
|
62
70
|
|
|
63
|
-
|
|
71
|
+
The ``uv tool update-shell`` command adds the installed commands to your
|
|
72
|
+
shell's PATH. Restart your shell after running ``update-shell``.
|
|
64
73
|
|
|
65
|
-
|
|
74
|
+
To install with ``pipx_``, run the following as a regular user:
|
|
66
75
|
|
|
67
|
-
|
|
76
|
+
.. code-block:: console
|
|
68
77
|
|
|
69
|
-
|
|
78
|
+
pipx install git-gerrit
|
|
79
|
+
pipx ensurepath
|
|
80
|
+
|
|
81
|
+
The ``pipx ensurepath`` command adds the installed commands to your shell's
|
|
82
|
+
PATH. Restart you shell after running ``ensurepath``.
|
|
83
|
+
|
|
84
|
+
Alternatively, you can install **git-gerrit** directly into a local virtual
|
|
85
|
+
environment with ``pip``::
|
|
70
86
|
|
|
71
87
|
$ cd <my-project-directory>
|
|
72
88
|
$ python3 -m venv .venv
|
|
@@ -74,7 +90,6 @@ If **pipx** is not available, then install in a project local virtualenv::
|
|
|
74
90
|
(.venv) $ pip install -U pip setuptools
|
|
75
91
|
(.venv) $ pip install git-gerrit
|
|
76
92
|
|
|
77
|
-
|
|
78
93
|
Setup
|
|
79
94
|
=====
|
|
80
95
|
|
|
@@ -101,6 +116,16 @@ Setup a local OpenAFS git repo::
|
|
|
101
116
|
$ git config --local gerrit.project openafs
|
|
102
117
|
$ git gerrit-install-hooks
|
|
103
118
|
|
|
119
|
+
Fetch all of the change commits from the upstream gerrit system to your local
|
|
120
|
+
repository and create the local sqlite database. This is a heavy weight operation
|
|
121
|
+
and is only needed for **git gerrit-log** and **git gerrit-number**::
|
|
122
|
+
|
|
123
|
+
$ git gerrit-sync
|
|
124
|
+
✔ Fetching changes from https://gerrit.example.com/example
|
|
125
|
+
✔ Updating local database
|
|
126
|
+
✔ Scanning commit messages
|
|
127
|
+
Done.
|
|
128
|
+
|
|
104
129
|
Find open gerrits on the master branch::
|
|
105
130
|
|
|
106
131
|
$ git gerrit-query is:open branch:master
|
|
@@ -163,14 +188,14 @@ Cherry-pick a gerrit onto the current branch::
|
|
|
163
188
|
|
|
164
189
|
Show gerrit numbers in the checked out branch in the local git repo::
|
|
165
190
|
|
|
166
|
-
$ git gerrit-log
|
|
191
|
+
$ git gerrit-log -n3
|
|
167
192
|
12958 f47cb2d Suppress statement not reached warnings under Solaris Studio
|
|
168
193
|
12957 306f0f3 afs: squash empty declaration warning
|
|
169
194
|
12955 e006609 libafs: git ignore build artifacts on Solaris
|
|
170
195
|
|
|
171
196
|
Show gerrit numbers by a revision in the local git repo::
|
|
172
197
|
|
|
173
|
-
$ git gerrit-log openafs-stable-1_8_0
|
|
198
|
+
$ git gerrit-log -n3 openafs-stable-1_8_0
|
|
174
199
|
12953 a08327f Update NEWS for 1.8.0 final release
|
|
175
200
|
12938 acb0e84 afs_pioctl: avoid -Wpointer-sign
|
|
176
201
|
12950 b73863b LINUX: fix RedHat 7.5 ENOTDIR issues
|
|
@@ -184,7 +209,7 @@ Show gerrit numbers by a range of revisions in the local git repo::
|
|
|
184
209
|
|
|
185
210
|
Show just the gerrit numbers and subjects in the local git repo::
|
|
186
211
|
|
|
187
|
-
$ git gerrit-log --format='{number}: {subject}'
|
|
212
|
+
$ git gerrit-log -n3 --format='{number}: {subject}'
|
|
188
213
|
12958: Suppress statement not reached warnings under Solaris Studio
|
|
189
214
|
12957: afs: squash empty declaration warning
|
|
190
215
|
12955: libafs: git ignore build artifacts on Solaris
|
|
@@ -12,7 +12,7 @@ with the **pygerrit2** package to access the Gerrit REST API.
|
|
|
12
12
|
Commands
|
|
13
13
|
========
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
**git-gerrit** commands::
|
|
16
16
|
|
|
17
17
|
git gerrit-checkout Fetch then checkout by gerrit number.
|
|
18
18
|
git gerrit-cherry-pick Cherry pick from upstream branch by gerrit number to make a new gerrit.
|
|
@@ -30,16 +30,32 @@ Installation
|
|
|
30
30
|
============
|
|
31
31
|
|
|
32
32
|
.. _pipx: https://pipx.pypa.io/stable/
|
|
33
|
+
.. _uv: https://github.com/astral-sh/uv
|
|
34
|
+
|
|
35
|
+
The recommended way to install **git-gerrit** is with `uv`_ or `pipx`_.
|
|
36
|
+
|
|
37
|
+
To install with ``uv``, run the following as a regular user:
|
|
33
38
|
|
|
34
|
-
|
|
39
|
+
.. code-block:: console
|
|
40
|
+
|
|
41
|
+
uv tool install git-gerrit
|
|
42
|
+
uv tool update-shell
|
|
35
43
|
|
|
36
|
-
|
|
44
|
+
The ``uv tool update-shell`` command adds the installed commands to your
|
|
45
|
+
shell's PATH. Restart your shell after running ``update-shell``.
|
|
37
46
|
|
|
38
|
-
|
|
47
|
+
To install with ``pipx_``, run the following as a regular user:
|
|
39
48
|
|
|
40
|
-
|
|
49
|
+
.. code-block:: console
|
|
41
50
|
|
|
42
|
-
|
|
51
|
+
pipx install git-gerrit
|
|
52
|
+
pipx ensurepath
|
|
53
|
+
|
|
54
|
+
The ``pipx ensurepath`` command adds the installed commands to your shell's
|
|
55
|
+
PATH. Restart you shell after running ``ensurepath``.
|
|
56
|
+
|
|
57
|
+
Alternatively, you can install **git-gerrit** directly into a local virtual
|
|
58
|
+
environment with ``pip``::
|
|
43
59
|
|
|
44
60
|
$ cd <my-project-directory>
|
|
45
61
|
$ python3 -m venv .venv
|
|
@@ -47,7 +63,6 @@ If **pipx** is not available, then install in a project local virtualenv::
|
|
|
47
63
|
(.venv) $ pip install -U pip setuptools
|
|
48
64
|
(.venv) $ pip install git-gerrit
|
|
49
65
|
|
|
50
|
-
|
|
51
66
|
Setup
|
|
52
67
|
=====
|
|
53
68
|
|
|
@@ -74,6 +89,16 @@ Setup a local OpenAFS git repo::
|
|
|
74
89
|
$ git config --local gerrit.project openafs
|
|
75
90
|
$ git gerrit-install-hooks
|
|
76
91
|
|
|
92
|
+
Fetch all of the change commits from the upstream gerrit system to your local
|
|
93
|
+
repository and create the local sqlite database. This is a heavy weight operation
|
|
94
|
+
and is only needed for **git gerrit-log** and **git gerrit-number**::
|
|
95
|
+
|
|
96
|
+
$ git gerrit-sync
|
|
97
|
+
✔ Fetching changes from https://gerrit.example.com/example
|
|
98
|
+
✔ Updating local database
|
|
99
|
+
✔ Scanning commit messages
|
|
100
|
+
Done.
|
|
101
|
+
|
|
77
102
|
Find open gerrits on the master branch::
|
|
78
103
|
|
|
79
104
|
$ git gerrit-query is:open branch:master
|
|
@@ -136,14 +161,14 @@ Cherry-pick a gerrit onto the current branch::
|
|
|
136
161
|
|
|
137
162
|
Show gerrit numbers in the checked out branch in the local git repo::
|
|
138
163
|
|
|
139
|
-
$ git gerrit-log
|
|
164
|
+
$ git gerrit-log -n3
|
|
140
165
|
12958 f47cb2d Suppress statement not reached warnings under Solaris Studio
|
|
141
166
|
12957 306f0f3 afs: squash empty declaration warning
|
|
142
167
|
12955 e006609 libafs: git ignore build artifacts on Solaris
|
|
143
168
|
|
|
144
169
|
Show gerrit numbers by a revision in the local git repo::
|
|
145
170
|
|
|
146
|
-
$ git gerrit-log openafs-stable-1_8_0
|
|
171
|
+
$ git gerrit-log -n3 openafs-stable-1_8_0
|
|
147
172
|
12953 a08327f Update NEWS for 1.8.0 final release
|
|
148
173
|
12938 acb0e84 afs_pioctl: avoid -Wpointer-sign
|
|
149
174
|
12950 b73863b LINUX: fix RedHat 7.5 ENOTDIR issues
|
|
@@ -157,7 +182,7 @@ Show gerrit numbers by a range of revisions in the local git repo::
|
|
|
157
182
|
|
|
158
183
|
Show just the gerrit numbers and subjects in the local git repo::
|
|
159
184
|
|
|
160
|
-
$ git gerrit-log --format='{number}: {subject}'
|
|
185
|
+
$ git gerrit-log -n3 --format='{number}: {subject}'
|
|
161
186
|
12958: Suppress statement not reached warnings under Solaris Studio
|
|
162
187
|
12957: afs: squash empty declaration warning
|
|
163
188
|
12955: libafs: git ignore build artifacts on Solaris
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
VERSION = "3.2.0"
|
|
@@ -492,25 +492,38 @@ def sync(limit=None):
|
|
|
492
492
|
parts = refname.split("/")
|
|
493
493
|
number = int(parts[3])
|
|
494
494
|
patchset = int(parts[4])
|
|
495
|
-
db.
|
|
495
|
+
db.add_patchset(number, patchset, commit_id)
|
|
496
496
|
spinner.spin()
|
|
497
497
|
|
|
498
498
|
# It is not practical to read every commit message, and normally, we only
|
|
499
499
|
# care about the current patchsets, so scan just the current patchsets
|
|
500
500
|
# (that is the max patchset number of each change number). Also, limit the
|
|
501
|
-
# number of changes to be scanned, since we normally care about
|
|
502
|
-
# most recent numbers. This
|
|
501
|
+
# number of changes to be scanned, since we normally care about just the
|
|
502
|
+
# most recent numbers. This amortizes the scanning, so the first
|
|
503
503
|
# git-gerrit-sync will scan a reasonable number of changes, and later syncs
|
|
504
504
|
# will process older changes.
|
|
505
|
+
no_change_id = 0
|
|
505
506
|
with Spinner("Scanning commit messages") as spinner:
|
|
506
507
|
with GitGerritDB() as db:
|
|
507
508
|
for c in db.get_current_patchsets(limit=limit):
|
|
508
509
|
if c['flags'] != 1:
|
|
510
|
+
number = c['number']
|
|
511
|
+
current_patchset = c['current_patchset']
|
|
509
512
|
commit_id = c['commit_id']
|
|
510
513
|
change_id = git.change_id(commit_id)
|
|
514
|
+
if change_id is None:
|
|
515
|
+
# Some early changes were merged before gerrit enforced
|
|
516
|
+
# a Change-Id trailer. Record the commit, but skip the
|
|
517
|
+
# gerrit_changes row, which is keyed by Change-Id.
|
|
518
|
+
no_change_id += 1
|
|
519
|
+
else:
|
|
520
|
+
db.record_change(number, current_patchset, change_id)
|
|
521
|
+
|
|
511
522
|
picked_from = git.cherry_picked_from(commit_id)
|
|
512
523
|
db.update_commit(commit_id, change_id, picked_from, 1)
|
|
513
524
|
spinner.spin()
|
|
514
525
|
|
|
526
|
+
if no_change_id:
|
|
527
|
+
print(f"Skipped {no_change_id} change(s) with no Change-Id.")
|
|
515
528
|
print("Done.")
|
|
516
529
|
return 0
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright (c) 2025 Sine Nomine Associates
|
|
1
|
+
# Copyright (c) 2025-2026 Sine Nomine Associates
|
|
2
2
|
#
|
|
3
3
|
# Redistribution and use in source and binary forms, with or without
|
|
4
4
|
# modification, are permitted provided that the following conditions are met:
|
|
@@ -25,7 +25,7 @@ from git_gerrit.git import Git
|
|
|
25
25
|
|
|
26
26
|
DATABASE = "git-gerrit.db"
|
|
27
27
|
MAGIC = 0x67697467 # "gitg"
|
|
28
|
-
SCHEMA_VERSION =
|
|
28
|
+
SCHEMA_VERSION = 2
|
|
29
29
|
MIGRATION_SCRIPTS = [
|
|
30
30
|
"""
|
|
31
31
|
CREATE TABLE changes (
|
|
@@ -42,6 +42,39 @@ MIGRATION_SCRIPTS = [
|
|
|
42
42
|
FOREIGN KEY (commit_id) REFERENCES commits(change_commit_id)
|
|
43
43
|
);
|
|
44
44
|
""",
|
|
45
|
+
"""
|
|
46
|
+
ALTER TABLE changes RENAME TO gerrit_patchsets;
|
|
47
|
+
ALTER TABLE gerrit_patchsets RENAME COLUMN change_number TO number;
|
|
48
|
+
ALTER TABLE gerrit_patchsets RENAME COLUMN change_patchset TO patchset;
|
|
49
|
+
ALTER TABLE gerrit_patchsets RENAME COLUMN change_commit_id TO commit_id;
|
|
50
|
+
|
|
51
|
+
ALTER TABLE commits RENAME COLUMN commit_change_id TO change_id;
|
|
52
|
+
ALTER TABLE commits RENAME COLUMN commit_picked_from TO cherry_picked_from;
|
|
53
|
+
ALTER TABLE commits RENAME COLUMN commit_flags TO flags;
|
|
54
|
+
|
|
55
|
+
/* One canonical row per Change-Id. When several gerrit changes share a
|
|
56
|
+
Change-Id (a cherry-pick that reused it instead of generating a new
|
|
57
|
+
one), the lowest number is canonical and the rest are recorded in
|
|
58
|
+
gerrit_duplicate_changes. */
|
|
59
|
+
CREATE TABLE gerrit_changes (
|
|
60
|
+
number INTEGER PRIMARY KEY,
|
|
61
|
+
current_patchset INTEGER,
|
|
62
|
+
change_id TEXT NOT NULL UNIQUE,
|
|
63
|
+
merged_as TEXT /* NULL if not merged */
|
|
64
|
+
);
|
|
65
|
+
CREATE TABLE gerrit_duplicate_changes (
|
|
66
|
+
number INTEGER PRIMARY KEY, /* A non-canonical change number. */
|
|
67
|
+
change_id TEXT NOT NULL, /* Its Change-Id. */
|
|
68
|
+
canonical_number INTEGER NOT NULL, /* The canonical change number. */
|
|
69
|
+
FOREIGN KEY (canonical_number) REFERENCES gerrit_changes(number)
|
|
70
|
+
);
|
|
71
|
+
CREATE INDEX gerrit_duplicate_changes_change_id
|
|
72
|
+
ON gerrit_duplicate_changes (change_id);
|
|
73
|
+
|
|
74
|
+
/* Rescan every commit message so the canonical/duplicate split is built
|
|
75
|
+
from the whole history, not just changes seen since the upgrade. */
|
|
76
|
+
UPDATE commits SET flags = 0;
|
|
77
|
+
""",
|
|
45
78
|
]
|
|
46
79
|
|
|
47
80
|
|
|
@@ -163,9 +196,9 @@ class GitGerritDB:
|
|
|
163
196
|
data = None
|
|
164
197
|
return data
|
|
165
198
|
|
|
166
|
-
def
|
|
199
|
+
def add_patchset(self, number, patchset, commit_id):
|
|
167
200
|
"""
|
|
168
|
-
Adds a new
|
|
201
|
+
Adds a new patchset to the database.
|
|
169
202
|
|
|
170
203
|
Args:
|
|
171
204
|
number (int): The change number.
|
|
@@ -176,21 +209,128 @@ class GitGerritDB:
|
|
|
176
209
|
cursor.execute(
|
|
177
210
|
"""
|
|
178
211
|
INSERT OR IGNORE INTO commits
|
|
179
|
-
(commit_id,
|
|
212
|
+
(commit_id, flags)
|
|
180
213
|
VALUES (?, 0)
|
|
181
214
|
""",
|
|
182
215
|
(commit_id,),
|
|
183
216
|
)
|
|
184
217
|
cursor.execute(
|
|
185
218
|
"""
|
|
186
|
-
INSERT OR IGNORE INTO
|
|
187
|
-
(
|
|
219
|
+
INSERT OR IGNORE INTO gerrit_patchsets
|
|
220
|
+
(number, patchset, commit_id)
|
|
188
221
|
VALUES (?, ?, ?)
|
|
189
222
|
""",
|
|
190
223
|
(number, patchset, commit_id),
|
|
191
224
|
)
|
|
192
225
|
self._dirty = True
|
|
193
226
|
|
|
227
|
+
def record_change(self, number, current_patchset, change_id):
|
|
228
|
+
"""
|
|
229
|
+
Records a change, keeping gerrit_changes to one row per Change-Id.
|
|
230
|
+
|
|
231
|
+
The canonical change for a Change-Id is the one with the lowest
|
|
232
|
+
number. Any other change numbers that carry the same Change-Id are
|
|
233
|
+
moved to gerrit_duplicate_changes.
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
number (int): The gerrit change number.
|
|
237
|
+
current_patchset (int): The most recent patchset number.
|
|
238
|
+
change_id (str): The gerrit Change-Id.
|
|
239
|
+
|
|
240
|
+
Returns:
|
|
241
|
+
bool: True if `number` is the canonical change, False if it was
|
|
242
|
+
recorded as a duplicate.
|
|
243
|
+
"""
|
|
244
|
+
with Cursor(self) as cursor:
|
|
245
|
+
cursor.execute(
|
|
246
|
+
"SELECT number FROM gerrit_changes WHERE change_id = ?",
|
|
247
|
+
(change_id,),
|
|
248
|
+
)
|
|
249
|
+
row = cursor.fetchone()
|
|
250
|
+
canonical = row["number"] if row else None
|
|
251
|
+
|
|
252
|
+
if canonical == number:
|
|
253
|
+
# Rescan of the canonical change; just refresh the patchset.
|
|
254
|
+
cursor.execute(
|
|
255
|
+
"UPDATE gerrit_changes SET current_patchset = ? "
|
|
256
|
+
"WHERE number = ?",
|
|
257
|
+
(current_patchset, number),
|
|
258
|
+
)
|
|
259
|
+
self._dirty = True
|
|
260
|
+
return True
|
|
261
|
+
|
|
262
|
+
# Forget any stale record of this number before re-recording it.
|
|
263
|
+
cursor.execute("DELETE FROM gerrit_changes WHERE number = ?", (number,))
|
|
264
|
+
cursor.execute(
|
|
265
|
+
"DELETE FROM gerrit_duplicate_changes WHERE number = ?", (number,)
|
|
266
|
+
)
|
|
267
|
+
self._dirty = True
|
|
268
|
+
|
|
269
|
+
if canonical is not None and number > canonical:
|
|
270
|
+
# A lower-numbered change is canonical; sidebar this one.
|
|
271
|
+
cursor.execute(
|
|
272
|
+
"INSERT INTO gerrit_duplicate_changes "
|
|
273
|
+
"(number, change_id, canonical_number) VALUES (?, ?, ?)",
|
|
274
|
+
(number, change_id, canonical),
|
|
275
|
+
)
|
|
276
|
+
return False
|
|
277
|
+
|
|
278
|
+
# `number` becomes canonical: the Change-Id is new, or `number` is
|
|
279
|
+
# lower than the current canonical and takes it over. Demote the
|
|
280
|
+
# old canonical and re-point its duplicates.
|
|
281
|
+
demoted = []
|
|
282
|
+
if canonical is not None:
|
|
283
|
+
cursor.execute(
|
|
284
|
+
"SELECT number FROM gerrit_duplicate_changes "
|
|
285
|
+
"WHERE change_id = ?",
|
|
286
|
+
(change_id,),
|
|
287
|
+
)
|
|
288
|
+
demoted = [r["number"] for r in cursor.fetchall()]
|
|
289
|
+
demoted.append(canonical)
|
|
290
|
+
cursor.execute(
|
|
291
|
+
"DELETE FROM gerrit_duplicate_changes WHERE change_id = ?",
|
|
292
|
+
(change_id,),
|
|
293
|
+
)
|
|
294
|
+
cursor.execute(
|
|
295
|
+
"DELETE FROM gerrit_changes WHERE number = ?", (canonical,)
|
|
296
|
+
)
|
|
297
|
+
cursor.execute(
|
|
298
|
+
"INSERT INTO gerrit_changes "
|
|
299
|
+
"(number, current_patchset, change_id) VALUES (?, ?, ?)",
|
|
300
|
+
(number, current_patchset, change_id),
|
|
301
|
+
)
|
|
302
|
+
for dup in demoted:
|
|
303
|
+
cursor.execute(
|
|
304
|
+
"INSERT INTO gerrit_duplicate_changes "
|
|
305
|
+
"(number, change_id, canonical_number) VALUES (?, ?, ?)",
|
|
306
|
+
(dup, change_id, number),
|
|
307
|
+
)
|
|
308
|
+
return True
|
|
309
|
+
|
|
310
|
+
def get_change(self, change_id):
|
|
311
|
+
"""
|
|
312
|
+
Returns the canonical change for a Change-Id, or None.
|
|
313
|
+
|
|
314
|
+
Args:
|
|
315
|
+
change_id (str): The gerrit Change-Id.
|
|
316
|
+
|
|
317
|
+
Returns:
|
|
318
|
+
dict: The gerrit_changes row, or None if the Change-Id is unknown.
|
|
319
|
+
"""
|
|
320
|
+
with Cursor(self) as cursor:
|
|
321
|
+
cursor.execute(
|
|
322
|
+
"SELECT number, current_patchset, change_id, merged_as "
|
|
323
|
+
"FROM gerrit_changes WHERE change_id = ?",
|
|
324
|
+
(change_id,),
|
|
325
|
+
)
|
|
326
|
+
return self._as_dict(cursor.fetchone())
|
|
327
|
+
|
|
328
|
+
def count_duplicate_changes(self):
|
|
329
|
+
"""Returns the number of rows in gerrit_duplicate_changes."""
|
|
330
|
+
with Cursor(self) as cursor:
|
|
331
|
+
cursor.execute("SELECT COUNT(*) FROM gerrit_duplicate_changes")
|
|
332
|
+
return cursor.fetchone()[0]
|
|
333
|
+
|
|
194
334
|
def update_commit(self, commit_id, change_id, picked_from, flags):
|
|
195
335
|
"""
|
|
196
336
|
Updates the details of a commit.
|
|
@@ -205,7 +345,7 @@ class GitGerritDB:
|
|
|
205
345
|
cursor.execute(
|
|
206
346
|
"""
|
|
207
347
|
UPDATE commits
|
|
208
|
-
SET
|
|
348
|
+
SET change_id = ?, cherry_picked_from = ?, flags = ?
|
|
209
349
|
WHERE commit_id == ?
|
|
210
350
|
""",
|
|
211
351
|
(change_id, picked_from, flags, commit_id),
|
|
@@ -214,13 +354,16 @@ class GitGerritDB:
|
|
|
214
354
|
|
|
215
355
|
def get_current_patchsets(self, limit=None):
|
|
216
356
|
"""
|
|
217
|
-
Retrieves the current
|
|
357
|
+
Retrieves the current patchset of every change.
|
|
358
|
+
|
|
359
|
+
Yields one row per change number: its highest-numbered patchset
|
|
360
|
+
joined with the commit details, ordered by change number descending.
|
|
218
361
|
|
|
219
362
|
Args:
|
|
220
|
-
limit (int, optional): The maximum number of
|
|
363
|
+
limit (int, optional): The maximum number of changes to retrieve.
|
|
221
364
|
|
|
222
365
|
Yields:
|
|
223
|
-
dict: A dictionary representing a
|
|
366
|
+
dict: A dictionary representing the current patchset of a change.
|
|
224
367
|
"""
|
|
225
368
|
if self._dirty:
|
|
226
369
|
self._conn.commit()
|
|
@@ -235,16 +378,16 @@ class GitGerritDB:
|
|
|
235
378
|
cursor.execute(
|
|
236
379
|
f"""
|
|
237
380
|
SELECT
|
|
238
|
-
|
|
239
|
-
MAX(
|
|
240
|
-
|
|
241
|
-
co.
|
|
242
|
-
co.
|
|
243
|
-
co.
|
|
244
|
-
FROM
|
|
245
|
-
LEFT JOIN commits AS co ON co.commit_id =
|
|
246
|
-
GROUP BY
|
|
247
|
-
ORDER BY
|
|
381
|
+
ps.number AS number,
|
|
382
|
+
MAX(ps.patchset) AS current_patchset,
|
|
383
|
+
ps.commit_id AS commit_id,
|
|
384
|
+
co.change_id AS change_id,
|
|
385
|
+
co.cherry_picked_from AS cherry_picked_from,
|
|
386
|
+
co.flags AS flags
|
|
387
|
+
FROM gerrit_patchsets AS ps
|
|
388
|
+
LEFT JOIN commits AS co ON co.commit_id = ps.commit_id
|
|
389
|
+
GROUP BY ps.number
|
|
390
|
+
ORDER BY ps.number DESC
|
|
248
391
|
{limit_clause}
|
|
249
392
|
"""
|
|
250
393
|
)
|
|
@@ -269,17 +412,17 @@ class GitGerritDB:
|
|
|
269
412
|
cursor.execute(
|
|
270
413
|
"""
|
|
271
414
|
SELECT
|
|
272
|
-
|
|
273
|
-
MAX(
|
|
274
|
-
|
|
275
|
-
co.
|
|
276
|
-
co.
|
|
277
|
-
co.
|
|
278
|
-
FROM
|
|
279
|
-
LEFT JOIN commits AS co ON co.commit_id =
|
|
280
|
-
WHERE
|
|
281
|
-
GROUP BY
|
|
282
|
-
ORDER BY
|
|
415
|
+
ps.number AS number,
|
|
416
|
+
MAX(ps.patchset) AS current_patchset,
|
|
417
|
+
ps.commit_id AS commit_id,
|
|
418
|
+
co.change_id AS change_id,
|
|
419
|
+
co.cherry_picked_from AS cherry_picked_from,
|
|
420
|
+
co.flags AS flags
|
|
421
|
+
FROM gerrit_patchsets AS ps
|
|
422
|
+
LEFT JOIN commits AS co ON co.commit_id = ps.commit_id
|
|
423
|
+
WHERE ps.number = ?
|
|
424
|
+
GROUP BY ps.number
|
|
425
|
+
ORDER BY ps.number DESC
|
|
283
426
|
LIMIT 1
|
|
284
427
|
""",
|
|
285
428
|
(number,),
|
|
@@ -307,15 +450,15 @@ class GitGerritDB:
|
|
|
307
450
|
cursor.execute(
|
|
308
451
|
"""
|
|
309
452
|
SELECT
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
co.
|
|
314
|
-
co.
|
|
315
|
-
co.
|
|
316
|
-
FROM
|
|
317
|
-
LEFT JOIN commits AS co ON co.commit_id =
|
|
318
|
-
WHERE
|
|
453
|
+
ps.number AS number,
|
|
454
|
+
ps.patchset AS patchset,
|
|
455
|
+
ps.commit_id AS commit_id,
|
|
456
|
+
co.change_id AS change_id,
|
|
457
|
+
co.cherry_picked_from AS cherry_picked_from,
|
|
458
|
+
co.flags AS flags
|
|
459
|
+
FROM gerrit_patchsets AS ps
|
|
460
|
+
LEFT JOIN commits AS co ON co.commit_id = ps.commit_id
|
|
461
|
+
WHERE ps.commit_id = ?
|
|
319
462
|
LIMIT 1
|
|
320
463
|
""",
|
|
321
464
|
(commit_id,),
|
|
@@ -345,11 +488,11 @@ class GitGerritDB:
|
|
|
345
488
|
"""
|
|
346
489
|
SELECT
|
|
347
490
|
commit_id,
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
491
|
+
change_id,
|
|
492
|
+
cherry_picked_from,
|
|
493
|
+
flags
|
|
351
494
|
FROM commits
|
|
352
|
-
WHERE
|
|
495
|
+
WHERE cherry_picked_from = ?
|
|
353
496
|
""",
|
|
354
497
|
(commit_picked_from,),
|
|
355
498
|
)
|
|
@@ -249,10 +249,13 @@ class Git:
|
|
|
249
249
|
return False
|
|
250
250
|
|
|
251
251
|
def change_id(self, sha1):
|
|
252
|
+
# Scan the raw commit message rather than git's %(trailers) parser.
|
|
253
|
+
# The trailer parser rejects the whole footer block when it contains
|
|
254
|
+
# non-trailer lines (e.g. the "FIXES 135591" convention used by
|
|
255
|
+
# OpenAFS), which would hide an otherwise valid Change-Id.
|
|
252
256
|
change_id = None
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
m = re.match(r'Change-Id: (I[0-9a-fA-F]+)', line)
|
|
257
|
+
for line in self.log(sha1, max_count=1, pretty="%B"):
|
|
258
|
+
m = re.match(r'^Change-Id: (I[0-9a-fA-F]+)', line)
|
|
256
259
|
if m:
|
|
257
260
|
change_id = m.group(1)
|
|
258
261
|
return change_id
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: git_gerrit
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.2.0
|
|
4
4
|
Summary: Gerrit review system command line tools.
|
|
5
5
|
Home-page: https://github.com/meffie/git-gerrit
|
|
6
6
|
Author: Michael Meffie
|
|
@@ -39,7 +39,7 @@ with the **pygerrit2** package to access the Gerrit REST API.
|
|
|
39
39
|
Commands
|
|
40
40
|
========
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
**git-gerrit** commands::
|
|
43
43
|
|
|
44
44
|
git gerrit-checkout Fetch then checkout by gerrit number.
|
|
45
45
|
git gerrit-cherry-pick Cherry pick from upstream branch by gerrit number to make a new gerrit.
|
|
@@ -57,16 +57,32 @@ Installation
|
|
|
57
57
|
============
|
|
58
58
|
|
|
59
59
|
.. _pipx: https://pipx.pypa.io/stable/
|
|
60
|
+
.. _uv: https://github.com/astral-sh/uv
|
|
61
|
+
|
|
62
|
+
The recommended way to install **git-gerrit** is with `uv`_ or `pipx`_.
|
|
63
|
+
|
|
64
|
+
To install with ``uv``, run the following as a regular user:
|
|
60
65
|
|
|
61
|
-
|
|
66
|
+
.. code-block:: console
|
|
67
|
+
|
|
68
|
+
uv tool install git-gerrit
|
|
69
|
+
uv tool update-shell
|
|
62
70
|
|
|
63
|
-
|
|
71
|
+
The ``uv tool update-shell`` command adds the installed commands to your
|
|
72
|
+
shell's PATH. Restart your shell after running ``update-shell``.
|
|
64
73
|
|
|
65
|
-
|
|
74
|
+
To install with ``pipx_``, run the following as a regular user:
|
|
66
75
|
|
|
67
|
-
|
|
76
|
+
.. code-block:: console
|
|
68
77
|
|
|
69
|
-
|
|
78
|
+
pipx install git-gerrit
|
|
79
|
+
pipx ensurepath
|
|
80
|
+
|
|
81
|
+
The ``pipx ensurepath`` command adds the installed commands to your shell's
|
|
82
|
+
PATH. Restart you shell after running ``ensurepath``.
|
|
83
|
+
|
|
84
|
+
Alternatively, you can install **git-gerrit** directly into a local virtual
|
|
85
|
+
environment with ``pip``::
|
|
70
86
|
|
|
71
87
|
$ cd <my-project-directory>
|
|
72
88
|
$ python3 -m venv .venv
|
|
@@ -74,7 +90,6 @@ If **pipx** is not available, then install in a project local virtualenv::
|
|
|
74
90
|
(.venv) $ pip install -U pip setuptools
|
|
75
91
|
(.venv) $ pip install git-gerrit
|
|
76
92
|
|
|
77
|
-
|
|
78
93
|
Setup
|
|
79
94
|
=====
|
|
80
95
|
|
|
@@ -101,6 +116,16 @@ Setup a local OpenAFS git repo::
|
|
|
101
116
|
$ git config --local gerrit.project openafs
|
|
102
117
|
$ git gerrit-install-hooks
|
|
103
118
|
|
|
119
|
+
Fetch all of the change commits from the upstream gerrit system to your local
|
|
120
|
+
repository and create the local sqlite database. This is a heavy weight operation
|
|
121
|
+
and is only needed for **git gerrit-log** and **git gerrit-number**::
|
|
122
|
+
|
|
123
|
+
$ git gerrit-sync
|
|
124
|
+
✔ Fetching changes from https://gerrit.example.com/example
|
|
125
|
+
✔ Updating local database
|
|
126
|
+
✔ Scanning commit messages
|
|
127
|
+
Done.
|
|
128
|
+
|
|
104
129
|
Find open gerrits on the master branch::
|
|
105
130
|
|
|
106
131
|
$ git gerrit-query is:open branch:master
|
|
@@ -163,14 +188,14 @@ Cherry-pick a gerrit onto the current branch::
|
|
|
163
188
|
|
|
164
189
|
Show gerrit numbers in the checked out branch in the local git repo::
|
|
165
190
|
|
|
166
|
-
$ git gerrit-log
|
|
191
|
+
$ git gerrit-log -n3
|
|
167
192
|
12958 f47cb2d Suppress statement not reached warnings under Solaris Studio
|
|
168
193
|
12957 306f0f3 afs: squash empty declaration warning
|
|
169
194
|
12955 e006609 libafs: git ignore build artifacts on Solaris
|
|
170
195
|
|
|
171
196
|
Show gerrit numbers by a revision in the local git repo::
|
|
172
197
|
|
|
173
|
-
$ git gerrit-log openafs-stable-1_8_0
|
|
198
|
+
$ git gerrit-log -n3 openafs-stable-1_8_0
|
|
174
199
|
12953 a08327f Update NEWS for 1.8.0 final release
|
|
175
200
|
12938 acb0e84 afs_pioctl: avoid -Wpointer-sign
|
|
176
201
|
12950 b73863b LINUX: fix RedHat 7.5 ENOTDIR issues
|
|
@@ -184,7 +209,7 @@ Show gerrit numbers by a range of revisions in the local git repo::
|
|
|
184
209
|
|
|
185
210
|
Show just the gerrit numbers and subjects in the local git repo::
|
|
186
211
|
|
|
187
|
-
$ git gerrit-log --format='{number}: {subject}'
|
|
212
|
+
$ git gerrit-log -n3 --format='{number}: {subject}'
|
|
188
213
|
12958: Suppress statement not reached warnings under Solaris Studio
|
|
189
214
|
12957: afs: squash empty declaration warning
|
|
190
215
|
12955: libafs: git ignore build artifacts on Solaris
|
|
@@ -11,6 +11,7 @@ from git_gerrit.core import (
|
|
|
11
11
|
sync,
|
|
12
12
|
update,
|
|
13
13
|
)
|
|
14
|
+
from git_gerrit.db import GitGerritDB, Cursor
|
|
14
15
|
from git_gerrit.error import GitGerritError, GitGerritNotFoundError
|
|
15
16
|
|
|
16
17
|
|
|
@@ -105,10 +106,40 @@ def test_get_current_change__not_found(mock_modules):
|
|
|
105
106
|
|
|
106
107
|
def test_sync(capsys, mock_modules):
|
|
107
108
|
sync()
|
|
108
|
-
output = capsys.readouterr().out
|
|
109
|
-
print(output)
|
|
109
|
+
output = capsys.readouterr().out
|
|
110
110
|
assert os.path.exists("mock-fetch")
|
|
111
111
|
with open("mock-fetch", "r") as f:
|
|
112
112
|
mock_fetch = f.read().splitlines()
|
|
113
113
|
assert mock_fetch[0] == "https://gerrit.example.org/mayhem"
|
|
114
114
|
assert mock_fetch[1] == "refs/changes/*:refs/changes/*"
|
|
115
|
+
|
|
116
|
+
# The mocked show-ref reports three changes:
|
|
117
|
+
# change 1 - current patchset (ps 3) has Change-Id Ibc6dab9f...
|
|
118
|
+
# change 2 - current patchset has no Change-Id trailer
|
|
119
|
+
# change 3 - current patchset reuses change 1's Change-Id
|
|
120
|
+
with GitGerritDB() as db:
|
|
121
|
+
with Cursor(db) as cursor:
|
|
122
|
+
cursor.execute(
|
|
123
|
+
"SELECT number, current_patchset, change_id FROM gerrit_changes "
|
|
124
|
+
"ORDER BY number"
|
|
125
|
+
)
|
|
126
|
+
changes = [tuple(row) for row in cursor.fetchall()]
|
|
127
|
+
with Cursor(db) as cursor:
|
|
128
|
+
cursor.execute(
|
|
129
|
+
"SELECT number, canonical_number FROM gerrit_duplicate_changes"
|
|
130
|
+
)
|
|
131
|
+
duplicates = [tuple(row) for row in cursor.fetchall()]
|
|
132
|
+
with Cursor(db) as cursor:
|
|
133
|
+
cursor.execute(
|
|
134
|
+
"SELECT flags FROM commits WHERE commit_id = ?", (f"{4:040}",)
|
|
135
|
+
)
|
|
136
|
+
skipped_commit = cursor.fetchone()
|
|
137
|
+
|
|
138
|
+
cid = "Ibc6dab9f4a99d693ea03891ed7222fed9a07a85a"
|
|
139
|
+
# Change 1 is canonical (lowest number); change 3 is sidebarred as a
|
|
140
|
+
# duplicate; change 2 is skipped for want of a Change-Id.
|
|
141
|
+
assert changes == [(1, 3, cid)]
|
|
142
|
+
assert duplicates == [(3, 1)]
|
|
143
|
+
assert "Skipped 1 change(s) with no Change-Id." in output
|
|
144
|
+
# The skipped change's commit is still recorded and marked scanned.
|
|
145
|
+
assert skipped_commit["flags"] == 1
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from git_gerrit.db import GitGerritDB, Cursor, SCHEMA_VERSION
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@pytest.fixture
|
|
7
|
+
def db(mock_modules):
|
|
8
|
+
with GitGerritDB() as db:
|
|
9
|
+
yield db
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@pytest.fixture
|
|
13
|
+
def staged_db(mock_modules):
|
|
14
|
+
with GitGerritDB() as db:
|
|
15
|
+
db.add_patchset(101, 1, "aaa")
|
|
16
|
+
db.add_patchset(101, 2, "bbb")
|
|
17
|
+
db.add_patchset(102, 1, "ccc")
|
|
18
|
+
db.add_patchset(103, 1, "ddd")
|
|
19
|
+
db.add_patchset(103, 2, "eee")
|
|
20
|
+
db.add_patchset(103, 3, "fff")
|
|
21
|
+
db.update_commit("bbb", "I101", None, 0)
|
|
22
|
+
db.update_commit("ccc", "I102", None, 1)
|
|
23
|
+
db.update_commit("fff", "I103", "ggg", 0)
|
|
24
|
+
yield db
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_db_get_schema_version__returns_current_schema_version(db):
|
|
28
|
+
assert db._get_schema_version() == SCHEMA_VERSION
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_db_init__creates_tables(db):
|
|
32
|
+
with Cursor(db) as cursor:
|
|
33
|
+
cursor.execute("SELECT name FROM sqlite_master WHERE type='table'")
|
|
34
|
+
names = [row['name'] for row in cursor.fetchall()]
|
|
35
|
+
assert "commits" in names
|
|
36
|
+
assert "gerrit_patchsets" in names
|
|
37
|
+
assert "gerrit_changes" in names
|
|
38
|
+
assert "gerrit_duplicate_changes" in names
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_db_add_patchset__inserts_into_tables(db):
|
|
42
|
+
db.add_patchset(123, 1, "abc")
|
|
43
|
+
with Cursor(db) as cursor:
|
|
44
|
+
cursor.execute("SELECT * FROM gerrit_patchsets WHERE number=123")
|
|
45
|
+
change = cursor.fetchone()
|
|
46
|
+
assert change is not None
|
|
47
|
+
assert change["patchset"] == 1
|
|
48
|
+
assert change["commit_id"] == "abc"
|
|
49
|
+
|
|
50
|
+
cursor.execute("SELECT * FROM commits WHERE commit_id='abc'")
|
|
51
|
+
commit = cursor.fetchone()
|
|
52
|
+
assert commit is not None
|
|
53
|
+
assert commit["flags"] == 0
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _dup_rows(db):
|
|
57
|
+
with Cursor(db) as cursor:
|
|
58
|
+
cursor.execute(
|
|
59
|
+
"SELECT number, change_id, canonical_number "
|
|
60
|
+
"FROM gerrit_duplicate_changes ORDER BY number"
|
|
61
|
+
)
|
|
62
|
+
return [tuple(row) for row in cursor.fetchall()]
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _change_rows(db):
|
|
66
|
+
with Cursor(db) as cursor:
|
|
67
|
+
cursor.execute(
|
|
68
|
+
"SELECT number, current_patchset, change_id "
|
|
69
|
+
"FROM gerrit_changes ORDER BY number"
|
|
70
|
+
)
|
|
71
|
+
return [tuple(row) for row in cursor.fetchall()]
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def test_db_record_change__inserts_and_updates_canonical(db):
|
|
75
|
+
assert db.record_change(200, 1, "I200") is True
|
|
76
|
+
assert _change_rows(db) == [(200, 1, "I200")]
|
|
77
|
+
|
|
78
|
+
# A rescan of the same change refreshes the patchset in place.
|
|
79
|
+
assert db.record_change(200, 2, "I200") is True
|
|
80
|
+
assert _change_rows(db) == [(200, 2, "I200")]
|
|
81
|
+
assert _dup_rows(db) == []
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def test_db_record_change__sidebars_higher_numbered_duplicate(db):
|
|
85
|
+
db.record_change(300, 1, "Ishared")
|
|
86
|
+
assert db.record_change(305, 1, "Ishared") is False
|
|
87
|
+
|
|
88
|
+
assert _change_rows(db) == [(300, 1, "Ishared")]
|
|
89
|
+
assert _dup_rows(db) == [(305, "Ishared", 300)]
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def test_db_record_change__lower_number_takes_over_as_canonical(db):
|
|
93
|
+
# Changes arrive newest-first, as git-gerrit-sync scans them.
|
|
94
|
+
db.record_change(305, 1, "Ishared")
|
|
95
|
+
db.record_change(303, 1, "Ishared")
|
|
96
|
+
db.record_change(300, 1, "Ishared")
|
|
97
|
+
|
|
98
|
+
assert _change_rows(db) == [(300, 1, "Ishared")]
|
|
99
|
+
assert _dup_rows(db) == [(303, "Ishared", 300), (305, "Ishared", 300)]
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def test_db_record_change__canonical_stable_when_scanned_oldest_first(db):
|
|
103
|
+
db.record_change(300, 1, "Ishared")
|
|
104
|
+
db.record_change(303, 1, "Ishared")
|
|
105
|
+
db.record_change(305, 1, "Ishared")
|
|
106
|
+
|
|
107
|
+
assert _change_rows(db) == [(300, 1, "Ishared")]
|
|
108
|
+
assert _dup_rows(db) == [(303, "Ishared", 300), (305, "Ishared", 300)]
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def test_db_record_change__is_idempotent_across_rescans(db):
|
|
112
|
+
for _ in range(2):
|
|
113
|
+
db.record_change(305, 1, "Ishared")
|
|
114
|
+
db.record_change(303, 2, "Ishared")
|
|
115
|
+
db.record_change(300, 3, "Ishared")
|
|
116
|
+
|
|
117
|
+
assert _change_rows(db) == [(300, 3, "Ishared")]
|
|
118
|
+
assert _dup_rows(db) == [(303, "Ishared", 300), (305, "Ishared", 300)]
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def test_db_get_change__returns_canonical_only(db):
|
|
122
|
+
db.record_change(300, 1, "Ishared")
|
|
123
|
+
db.record_change(305, 2, "Ishared")
|
|
124
|
+
|
|
125
|
+
assert db.get_change("Ishared")["number"] == 300
|
|
126
|
+
assert db.get_change("Imissing") is None
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def test_db_count_duplicate_changes(db):
|
|
130
|
+
db.record_change(300, 1, "Ishared")
|
|
131
|
+
db.record_change(305, 1, "Ishared")
|
|
132
|
+
db.record_change(400, 1, "Iunique")
|
|
133
|
+
|
|
134
|
+
assert db.count_duplicate_changes() == 1
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def test_db_update_commit__updates_commit_row(db):
|
|
138
|
+
db.add_patchset(123, 1, "abc")
|
|
139
|
+
db.update_commit("abc", "I123", "def", 1)
|
|
140
|
+
with Cursor(db) as cursor:
|
|
141
|
+
cursor.execute("SELECT * FROM commits WHERE commit_id='abc'")
|
|
142
|
+
commit = cursor.fetchone()
|
|
143
|
+
assert commit is not None
|
|
144
|
+
assert commit["change_id"] == "I123"
|
|
145
|
+
assert commit["cherry_picked_from"] == "def"
|
|
146
|
+
assert commit["flags"] == 1
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def test_db_get_current_patchsets__returns_latest_patchsets(staged_db):
|
|
150
|
+
db = staged_db
|
|
151
|
+
|
|
152
|
+
# Test without filters
|
|
153
|
+
changes = list(db.get_current_patchsets())
|
|
154
|
+
assert len(changes) == 3
|
|
155
|
+
assert changes[0]["number"] == 103
|
|
156
|
+
assert changes[0]["current_patchset"] == 3
|
|
157
|
+
assert changes[0]["commit_id"] == "fff"
|
|
158
|
+
assert changes[0]["change_id"] == "I103"
|
|
159
|
+
assert changes[0]["cherry_picked_from"] == "ggg"
|
|
160
|
+
assert changes[0]["flags"] == 0
|
|
161
|
+
|
|
162
|
+
assert changes[1]["number"] == 102
|
|
163
|
+
assert changes[1]["current_patchset"] == 1
|
|
164
|
+
assert changes[1]["commit_id"] == "ccc"
|
|
165
|
+
assert changes[1]["change_id"] == "I102"
|
|
166
|
+
assert changes[1]["flags"] == 1
|
|
167
|
+
|
|
168
|
+
assert changes[2]["number"] == 101
|
|
169
|
+
assert changes[2]["current_patchset"] == 2
|
|
170
|
+
assert changes[2]["commit_id"] == "bbb"
|
|
171
|
+
assert changes[2]["change_id"] == "I101"
|
|
172
|
+
assert changes[2]["flags"] == 0
|
|
173
|
+
|
|
174
|
+
# Test with limit
|
|
175
|
+
changes = list(db.get_current_patchsets(limit=1))
|
|
176
|
+
assert len(changes) == 1
|
|
177
|
+
assert changes[0]["number"] == 103
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def test_db_get_current_patchset__returns_latest_patchset_by_number(staged_db):
|
|
181
|
+
db = staged_db
|
|
182
|
+
change = db.get_current_patchset_by_number(101)
|
|
183
|
+
assert change["number"] == 101
|
|
184
|
+
assert change["current_patchset"] == 2
|
|
185
|
+
assert change["commit_id"] == "bbb"
|
|
186
|
+
assert change["change_id"] == "I101"
|
|
187
|
+
assert change["flags"] == 0
|
|
188
|
+
|
|
189
|
+
change = db.get_current_patchset_by_number(102)
|
|
190
|
+
assert change["number"] == 102
|
|
191
|
+
assert change["current_patchset"] == 1
|
|
192
|
+
assert change["commit_id"] == "ccc"
|
|
193
|
+
assert change["change_id"] == "I102"
|
|
194
|
+
assert change["flags"] == 1
|
|
195
|
+
|
|
196
|
+
change = db.get_current_patchset_by_number(103)
|
|
197
|
+
assert change["number"] == 103
|
|
198
|
+
assert change["current_patchset"] == 3
|
|
199
|
+
assert change["commit_id"] == "fff"
|
|
200
|
+
assert change["change_id"] == "I103"
|
|
201
|
+
assert change["cherry_picked_from"] == "ggg"
|
|
202
|
+
assert change["flags"] == 0
|
|
203
|
+
|
|
204
|
+
change = db.get_current_patchset_by_number(999)
|
|
205
|
+
assert change is None
|
|
@@ -84,5 +84,7 @@ def test_write_hook(git):
|
|
|
84
84
|
|
|
85
85
|
|
|
86
86
|
def test_change_id(git):
|
|
87
|
+
# The Change-Id is found even though the footer also contains non-trailer
|
|
88
|
+
# "FIXES" lines that defeat git's %(trailers) parser.
|
|
87
89
|
got = git.change_id("0" * 40)
|
|
88
|
-
assert got == "
|
|
90
|
+
assert got == "Ibc6dab9f4a99d693ea03891ed7222fed9a07a85a"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
VERSION = "3.0.0"
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import pytest
|
|
2
|
-
|
|
3
|
-
from git_gerrit.db import GitGerritDB, Cursor, SCHEMA_VERSION
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
@pytest.fixture
|
|
7
|
-
def db(mock_modules):
|
|
8
|
-
with GitGerritDB() as db:
|
|
9
|
-
yield db
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
@pytest.fixture
|
|
13
|
-
def staged_db(mock_modules):
|
|
14
|
-
with GitGerritDB() as db:
|
|
15
|
-
db.add_change(101, 1, "aaa")
|
|
16
|
-
db.add_change(101, 2, "bbb")
|
|
17
|
-
db.add_change(102, 1, "ccc")
|
|
18
|
-
db.add_change(103, 1, "ddd")
|
|
19
|
-
db.add_change(103, 2, "eee")
|
|
20
|
-
db.add_change(103, 3, "fff")
|
|
21
|
-
db.update_commit("bbb", "I101", None, 0)
|
|
22
|
-
db.update_commit("ccc", "I102", None, 1)
|
|
23
|
-
db.update_commit("fff", "I103", "ggg", 0)
|
|
24
|
-
yield db
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
def test_db_get_schema_version__returns_current_schema_version(db):
|
|
28
|
-
assert db._get_schema_version() == SCHEMA_VERSION
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def test_db_init__creates_tables(db):
|
|
32
|
-
with Cursor(db) as cursor:
|
|
33
|
-
cursor.execute("SELECT name FROM sqlite_master WHERE type='table'")
|
|
34
|
-
names = [row['name'] for row in cursor.fetchall()]
|
|
35
|
-
assert len(names) != 0
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
def test_db_add_change__inserts_into_tables(db):
|
|
39
|
-
db.add_change(123, 1, "abc")
|
|
40
|
-
with Cursor(db) as cursor:
|
|
41
|
-
cursor.execute("SELECT * FROM changes WHERE change_number=123")
|
|
42
|
-
change = cursor.fetchone()
|
|
43
|
-
assert change is not None
|
|
44
|
-
assert change["change_patchset"] == 1
|
|
45
|
-
assert change["change_commit_id"] == "abc"
|
|
46
|
-
|
|
47
|
-
cursor.execute("SELECT * FROM commits WHERE commit_id='abc'")
|
|
48
|
-
commit = cursor.fetchone()
|
|
49
|
-
assert commit is not None
|
|
50
|
-
assert commit["commit_flags"] == 0
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
def test_db_update_commit__updates_commit_row(db):
|
|
54
|
-
db.add_change(123, 1, "abc")
|
|
55
|
-
db.update_commit("abc", "I123", "def", 1)
|
|
56
|
-
with Cursor(db) as cursor:
|
|
57
|
-
cursor.execute("SELECT * FROM commits WHERE commit_id='abc'")
|
|
58
|
-
commit = cursor.fetchone()
|
|
59
|
-
assert commit is not None
|
|
60
|
-
assert commit["commit_change_id"] == "I123"
|
|
61
|
-
assert commit["commit_picked_from"] == "def"
|
|
62
|
-
assert commit["commit_flags"] == 1
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
def test_db_get_current_patchsets__returns_latest_patchsets(staged_db):
|
|
66
|
-
db = staged_db
|
|
67
|
-
|
|
68
|
-
# Test without filters
|
|
69
|
-
changes = list(db.get_current_patchsets())
|
|
70
|
-
assert len(changes) == 3
|
|
71
|
-
assert changes[0]["number"] == 103
|
|
72
|
-
assert changes[0]["current_patchset"] == 3
|
|
73
|
-
assert changes[0]["commit_id"] == "fff"
|
|
74
|
-
assert changes[0]["change_id"] == "I103"
|
|
75
|
-
assert changes[0]["cherry_picked_from"] == "ggg"
|
|
76
|
-
assert changes[0]["flags"] == 0
|
|
77
|
-
|
|
78
|
-
assert changes[1]["number"] == 102
|
|
79
|
-
assert changes[1]["current_patchset"] == 1
|
|
80
|
-
assert changes[1]["commit_id"] == "ccc"
|
|
81
|
-
assert changes[1]["change_id"] == "I102"
|
|
82
|
-
assert changes[1]["flags"] == 1
|
|
83
|
-
|
|
84
|
-
assert changes[2]["number"] == 101
|
|
85
|
-
assert changes[2]["current_patchset"] == 2
|
|
86
|
-
assert changes[2]["commit_id"] == "bbb"
|
|
87
|
-
assert changes[2]["change_id"] == "I101"
|
|
88
|
-
assert changes[2]["flags"] == 0
|
|
89
|
-
|
|
90
|
-
# Test with limit
|
|
91
|
-
changes = list(db.get_current_patchsets(limit=1))
|
|
92
|
-
assert len(changes) == 1
|
|
93
|
-
assert changes[0]["number"] == 103
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
def test_db_get_current_patchset__returns_latest_patchset_by_number(staged_db):
|
|
97
|
-
db = staged_db
|
|
98
|
-
change = db.get_current_patchset_by_number(101)
|
|
99
|
-
assert change["number"] == 101
|
|
100
|
-
assert change["current_patchset"] == 2
|
|
101
|
-
assert change["commit_id"] == "bbb"
|
|
102
|
-
assert change["change_id"] == "I101"
|
|
103
|
-
assert change["flags"] == 0
|
|
104
|
-
|
|
105
|
-
change = db.get_current_patchset_by_number(102)
|
|
106
|
-
assert change["number"] == 102
|
|
107
|
-
assert change["current_patchset"] == 1
|
|
108
|
-
assert change["commit_id"] == "ccc"
|
|
109
|
-
assert change["change_id"] == "I102"
|
|
110
|
-
assert change["flags"] == 1
|
|
111
|
-
|
|
112
|
-
change = db.get_current_patchset_by_number(103)
|
|
113
|
-
assert change["number"] == 103
|
|
114
|
-
assert change["current_patchset"] == 3
|
|
115
|
-
assert change["commit_id"] == "fff"
|
|
116
|
-
assert change["change_id"] == "I103"
|
|
117
|
-
assert change["cherry_picked_from"] == "ggg"
|
|
118
|
-
assert change["flags"] == 0
|
|
119
|
-
|
|
120
|
-
change = db.get_current_patchset_by_number(999)
|
|
121
|
-
assert change is None
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|