git-gerrit 3.0.0__tar.gz → 3.3.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.
Files changed (27) hide show
  1. {git_gerrit-3.0.0/git_gerrit.egg-info → git_gerrit-3.3.0}/PKG-INFO +38 -11
  2. {git_gerrit-3.0.0 → git_gerrit-3.3.0}/README.rst +35 -10
  3. git_gerrit-3.3.0/git_gerrit/__version__.py +1 -0
  4. {git_gerrit-3.0.0 → git_gerrit-3.3.0}/git_gerrit/cli.py +4 -1
  5. {git_gerrit-3.0.0 → git_gerrit-3.3.0}/git_gerrit/core.py +16 -3
  6. {git_gerrit-3.0.0 → git_gerrit-3.3.0}/git_gerrit/db.py +188 -45
  7. {git_gerrit-3.0.0 → git_gerrit-3.3.0}/git_gerrit/git.py +18 -4
  8. {git_gerrit-3.0.0 → git_gerrit-3.3.0/git_gerrit.egg-info}/PKG-INFO +38 -11
  9. {git_gerrit-3.0.0 → git_gerrit-3.3.0}/setup.py +1 -0
  10. {git_gerrit-3.0.0 → git_gerrit-3.3.0}/tests/test_core.py +33 -2
  11. git_gerrit-3.3.0/tests/test_db.py +205 -0
  12. {git_gerrit-3.0.0 → git_gerrit-3.3.0}/tests/test_git.py +3 -1
  13. git_gerrit-3.0.0/git_gerrit/__version__.py +0 -1
  14. git_gerrit-3.0.0/tests/test_db.py +0 -121
  15. {git_gerrit-3.0.0 → git_gerrit-3.3.0}/LICENSE.txt +0 -0
  16. {git_gerrit-3.0.0 → git_gerrit-3.3.0}/MANIFEST.in +0 -0
  17. {git_gerrit-3.0.0 → git_gerrit-3.3.0}/git_gerrit/__init__.py +0 -0
  18. {git_gerrit-3.0.0 → git_gerrit-3.3.0}/git_gerrit/error.py +0 -0
  19. {git_gerrit-3.0.0 → git_gerrit-3.3.0}/git_gerrit/spinner.py +0 -0
  20. {git_gerrit-3.0.0 → git_gerrit-3.3.0}/git_gerrit.egg-info/SOURCES.txt +0 -0
  21. {git_gerrit-3.0.0 → git_gerrit-3.3.0}/git_gerrit.egg-info/dependency_links.txt +0 -0
  22. {git_gerrit-3.0.0 → git_gerrit-3.3.0}/git_gerrit.egg-info/entry_points.txt +0 -0
  23. {git_gerrit-3.0.0 → git_gerrit-3.3.0}/git_gerrit.egg-info/requires.txt +0 -0
  24. {git_gerrit-3.0.0 → git_gerrit-3.3.0}/git_gerrit.egg-info/top_level.txt +0 -0
  25. {git_gerrit-3.0.0 → git_gerrit-3.3.0}/pyproject.toml +0 -0
  26. {git_gerrit-3.0.0 → git_gerrit-3.3.0}/setup.cfg +0 -0
  27. {git_gerrit-3.0.0 → git_gerrit-3.3.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.0.0
3
+ Version: 3.3.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
@@ -12,6 +12,7 @@ Classifier: Intended Audience :: Developers
12
12
  Classifier: Operating System :: POSIX
13
13
  Classifier: Programming Language :: Python :: 3
14
14
  Classifier: Topic :: Software Development
15
+ Description-Content-Type: text/x-rst
15
16
  License-File: LICENSE.txt
16
17
  Requires-Dist: sh
17
18
  Requires-Dist: pygerrit2
@@ -19,6 +20,7 @@ Dynamic: author
19
20
  Dynamic: author-email
20
21
  Dynamic: classifier
21
22
  Dynamic: description
23
+ Dynamic: description-content-type
22
24
  Dynamic: home-page
23
25
  Dynamic: license
24
26
  Dynamic: license-file
@@ -39,7 +41,7 @@ with the **pygerrit2** package to access the Gerrit REST API.
39
41
  Commands
40
42
  ========
41
43
 
42
- .. code-block:: console
44
+ **git-gerrit** commands::
43
45
 
44
46
  git gerrit-checkout Fetch then checkout by gerrit number.
45
47
  git gerrit-cherry-pick Cherry pick from upstream branch by gerrit number to make a new gerrit.
@@ -57,16 +59,32 @@ Installation
57
59
  ============
58
60
 
59
61
  .. _pipx: https://pipx.pypa.io/stable/
62
+ .. _uv: https://github.com/astral-sh/uv
63
+
64
+ The recommended way to install **git-gerrit** is with `uv`_ or `pipx`_.
65
+
66
+ To install with ``uv``, run the following as a regular user:
60
67
 
61
- Install with `pipx`_::
68
+ .. code-block:: console
69
+
70
+ uv tool install git-gerrit
71
+ uv tool update-shell
62
72
 
63
- $ pipx install git-gerrit
73
+ The ``uv tool update-shell`` command adds the installed commands to your
74
+ shell's PATH. Restart your shell after running ``update-shell``.
64
75
 
65
- Be sure your ``PATH`` has been updated to run **pipx** installed commands::
76
+ To install with ``pipx_``, run the following as a regular user:
66
77
 
67
- $ pipx ensurepath
78
+ .. code-block:: console
68
79
 
69
- If **pipx** is not available, then install in a project local virtualenv::
80
+ pipx install git-gerrit
81
+ pipx ensurepath
82
+
83
+ The ``pipx ensurepath`` command adds the installed commands to your shell's
84
+ PATH. Restart you shell after running ``ensurepath``.
85
+
86
+ Alternatively, you can install **git-gerrit** directly into a local virtual
87
+ environment with ``pip``::
70
88
 
71
89
  $ cd <my-project-directory>
72
90
  $ python3 -m venv .venv
@@ -74,7 +92,6 @@ If **pipx** is not available, then install in a project local virtualenv::
74
92
  (.venv) $ pip install -U pip setuptools
75
93
  (.venv) $ pip install git-gerrit
76
94
 
77
-
78
95
  Setup
79
96
  =====
80
97
 
@@ -101,6 +118,16 @@ Setup a local OpenAFS git repo::
101
118
  $ git config --local gerrit.project openafs
102
119
  $ git gerrit-install-hooks
103
120
 
121
+ Fetch all of the change commits from the upstream gerrit system to your local
122
+ repository and create the local sqlite database. This is a heavy weight operation
123
+ and is only needed for **git gerrit-log** and **git gerrit-number**::
124
+
125
+ $ git gerrit-sync
126
+ ✔ Fetching changes from https://gerrit.example.com/example
127
+ ✔ Updating local database
128
+ ✔ Scanning commit messages
129
+ Done.
130
+
104
131
  Find open gerrits on the master branch::
105
132
 
106
133
  $ git gerrit-query is:open branch:master
@@ -163,14 +190,14 @@ Cherry-pick a gerrit onto the current branch::
163
190
 
164
191
  Show gerrit numbers in the checked out branch in the local git repo::
165
192
 
166
- $ git gerrit-log
193
+ $ git gerrit-log -n3
167
194
  12958 f47cb2d Suppress statement not reached warnings under Solaris Studio
168
195
  12957 306f0f3 afs: squash empty declaration warning
169
196
  12955 e006609 libafs: git ignore build artifacts on Solaris
170
197
 
171
198
  Show gerrit numbers by a revision in the local git repo::
172
199
 
173
- $ git gerrit-log openafs-stable-1_8_0
200
+ $ git gerrit-log -n3 openafs-stable-1_8_0
174
201
  12953 a08327f Update NEWS for 1.8.0 final release
175
202
  12938 acb0e84 afs_pioctl: avoid -Wpointer-sign
176
203
  12950 b73863b LINUX: fix RedHat 7.5 ENOTDIR issues
@@ -184,7 +211,7 @@ Show gerrit numbers by a range of revisions in the local git repo::
184
211
 
185
212
  Show just the gerrit numbers and subjects in the local git repo::
186
213
 
187
- $ git gerrit-log --format='{number}: {subject}'
214
+ $ git gerrit-log -n3 --format='{number}: {subject}'
188
215
  12958: Suppress statement not reached warnings under Solaris Studio
189
216
  12957: afs: squash empty declaration warning
190
217
  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
- .. code-block:: console
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
- Install with `pipx`_::
39
+ .. code-block:: console
40
+
41
+ uv tool install git-gerrit
42
+ uv tool update-shell
35
43
 
36
- $ pipx install git-gerrit
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
- Be sure your ``PATH`` has been updated to run **pipx** installed commands::
47
+ To install with ``pipx_``, run the following as a regular user:
39
48
 
40
- $ pipx ensurepath
49
+ .. code-block:: console
41
50
 
42
- If **pipx** is not available, then install in a project local virtualenv::
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.3.0"
@@ -460,7 +460,10 @@ def main_git_gerrit_number(argv=None):
460
460
  for p in change['cherry_picked_to']:
461
461
  print(p or "")
462
462
  elif args.checkout:
463
- print(git.git.checkout(change['commit_id'], detach=True))
463
+ number = change['number']
464
+ patchset = change['current_patchset']
465
+ print(f"checking out {number},{patchset}")
466
+ git.checkout(change['commit_id'])
464
467
  elif args.show:
465
468
  print(git.git.show(change['commit_id']))
466
469
  else:
@@ -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.add_change(number, patchset, commit_id)
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 jus the
502
- # most recent numbers. This amoritizes the scanning, so the first
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 = 1
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 add_change(self, number, patchset, commit_id):
199
+ def add_patchset(self, number, patchset, commit_id):
167
200
  """
168
- Adds a new change to the database.
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, commit_flags)
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 changes
187
- (change_number, change_patchset, change_commit_id)
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 commit_change_id = ?, commit_picked_from = ?, commit_flags = ?
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 patchsets for all changes.
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 patchsets to retrieve.
363
+ limit (int, optional): The maximum number of changes to retrieve.
221
364
 
222
365
  Yields:
223
- dict: A dictionary representing a patchset.
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
- ch.change_number AS number,
239
- MAX(ch.change_patchset) AS current_patchset,
240
- ch.change_commit_id AS commit_id,
241
- co.commit_change_id AS change_id,
242
- co.commit_picked_from AS cherry_picked_from,
243
- co.commit_flags AS flags
244
- FROM changes AS ch
245
- LEFT JOIN commits AS co ON co.commit_id = ch.change_commit_id
246
- GROUP BY ch.change_number
247
- ORDER BY ch.change_number DESC
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
- ch.change_number AS number,
273
- MAX(ch.change_patchset) AS current_patchset,
274
- ch.change_commit_id AS commit_id,
275
- co.commit_change_id AS change_id,
276
- co.commit_picked_from AS cherry_picked_from,
277
- co.commit_flags AS flags
278
- FROM changes AS ch
279
- LEFT JOIN commits AS co ON co.commit_id = ch.change_commit_id
280
- WHERE ch.change_number = ?
281
- GROUP BY ch.change_number
282
- ORDER BY ch.change_number DESC
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
- ch.change_number AS number,
311
- ch.change_patchset AS patchset,
312
- ch.change_commit_id AS commit_id,
313
- co.commit_change_id AS change_id,
314
- co.commit_picked_from AS cherry_picked_from,
315
- co.commit_flags AS flags
316
- FROM changes AS ch
317
- LEFT JOIN commits AS co ON co.commit_id = ch.change_commit_id
318
- WHERE ch.change_commit_id = ?
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
- commit_change_id,
349
- commit_picked_from,
350
- commit_flags
491
+ change_id,
492
+ cherry_picked_from,
493
+ flags
351
494
  FROM commits
352
- WHERE commit_picked_from = ?
495
+ WHERE cherry_picked_from = ?
353
496
  """,
354
497
  (commit_picked_from,),
355
498
  )
@@ -22,6 +22,7 @@
22
22
  import os
23
23
  import re
24
24
  import sh
25
+ import sys
25
26
 
26
27
  import urllib.request
27
28
 
@@ -186,7 +187,17 @@ class Git:
186
187
 
187
188
  def checkout(self, refname):
188
189
  """Run git checkout to checkout a change."""
189
- self.git.checkout(refname)
190
+ try:
191
+ self.git(
192
+ "-c",
193
+ "advice.detachedHead=false",
194
+ "checkout",
195
+ refname,
196
+ _out=sys.stdout,
197
+ _err=sys.stderr,
198
+ )
199
+ except sh.ErrorReturnCode as e:
200
+ raise GitGerritError(f"Command failed: git checkout: {e.exit_code}")
190
201
 
191
202
  def log(self, refname=None, **options):
192
203
  """Run git log to show changes."""
@@ -249,10 +260,13 @@ class Git:
249
260
  return False
250
261
 
251
262
  def change_id(self, sha1):
263
+ # Scan the raw commit message rather than git's %(trailers) parser.
264
+ # The trailer parser rejects the whole footer block when it contains
265
+ # non-trailer lines (e.g. the "FIXES 135591" convention used by
266
+ # OpenAFS), which would hide an otherwise valid Change-Id.
252
267
  change_id = None
253
- format_ = "%(trailers:key=Change-Id)"
254
- for line in self.log(sha1, max_count=1, pretty=format_):
255
- m = re.match(r'Change-Id: (I[0-9a-fA-F]+)', line)
268
+ for line in self.log(sha1, max_count=1, pretty="%B"):
269
+ m = re.match(r'^Change-Id: (I[0-9a-fA-F]+)', line)
256
270
  if m:
257
271
  change_id = m.group(1)
258
272
  return change_id
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: git_gerrit
3
- Version: 3.0.0
3
+ Version: 3.3.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
@@ -12,6 +12,7 @@ Classifier: Intended Audience :: Developers
12
12
  Classifier: Operating System :: POSIX
13
13
  Classifier: Programming Language :: Python :: 3
14
14
  Classifier: Topic :: Software Development
15
+ Description-Content-Type: text/x-rst
15
16
  License-File: LICENSE.txt
16
17
  Requires-Dist: sh
17
18
  Requires-Dist: pygerrit2
@@ -19,6 +20,7 @@ Dynamic: author
19
20
  Dynamic: author-email
20
21
  Dynamic: classifier
21
22
  Dynamic: description
23
+ Dynamic: description-content-type
22
24
  Dynamic: home-page
23
25
  Dynamic: license
24
26
  Dynamic: license-file
@@ -39,7 +41,7 @@ with the **pygerrit2** package to access the Gerrit REST API.
39
41
  Commands
40
42
  ========
41
43
 
42
- .. code-block:: console
44
+ **git-gerrit** commands::
43
45
 
44
46
  git gerrit-checkout Fetch then checkout by gerrit number.
45
47
  git gerrit-cherry-pick Cherry pick from upstream branch by gerrit number to make a new gerrit.
@@ -57,16 +59,32 @@ Installation
57
59
  ============
58
60
 
59
61
  .. _pipx: https://pipx.pypa.io/stable/
62
+ .. _uv: https://github.com/astral-sh/uv
63
+
64
+ The recommended way to install **git-gerrit** is with `uv`_ or `pipx`_.
65
+
66
+ To install with ``uv``, run the following as a regular user:
60
67
 
61
- Install with `pipx`_::
68
+ .. code-block:: console
69
+
70
+ uv tool install git-gerrit
71
+ uv tool update-shell
62
72
 
63
- $ pipx install git-gerrit
73
+ The ``uv tool update-shell`` command adds the installed commands to your
74
+ shell's PATH. Restart your shell after running ``update-shell``.
64
75
 
65
- Be sure your ``PATH`` has been updated to run **pipx** installed commands::
76
+ To install with ``pipx_``, run the following as a regular user:
66
77
 
67
- $ pipx ensurepath
78
+ .. code-block:: console
68
79
 
69
- If **pipx** is not available, then install in a project local virtualenv::
80
+ pipx install git-gerrit
81
+ pipx ensurepath
82
+
83
+ The ``pipx ensurepath`` command adds the installed commands to your shell's
84
+ PATH. Restart you shell after running ``ensurepath``.
85
+
86
+ Alternatively, you can install **git-gerrit** directly into a local virtual
87
+ environment with ``pip``::
70
88
 
71
89
  $ cd <my-project-directory>
72
90
  $ python3 -m venv .venv
@@ -74,7 +92,6 @@ If **pipx** is not available, then install in a project local virtualenv::
74
92
  (.venv) $ pip install -U pip setuptools
75
93
  (.venv) $ pip install git-gerrit
76
94
 
77
-
78
95
  Setup
79
96
  =====
80
97
 
@@ -101,6 +118,16 @@ Setup a local OpenAFS git repo::
101
118
  $ git config --local gerrit.project openafs
102
119
  $ git gerrit-install-hooks
103
120
 
121
+ Fetch all of the change commits from the upstream gerrit system to your local
122
+ repository and create the local sqlite database. This is a heavy weight operation
123
+ and is only needed for **git gerrit-log** and **git gerrit-number**::
124
+
125
+ $ git gerrit-sync
126
+ ✔ Fetching changes from https://gerrit.example.com/example
127
+ ✔ Updating local database
128
+ ✔ Scanning commit messages
129
+ Done.
130
+
104
131
  Find open gerrits on the master branch::
105
132
 
106
133
  $ git gerrit-query is:open branch:master
@@ -163,14 +190,14 @@ Cherry-pick a gerrit onto the current branch::
163
190
 
164
191
  Show gerrit numbers in the checked out branch in the local git repo::
165
192
 
166
- $ git gerrit-log
193
+ $ git gerrit-log -n3
167
194
  12958 f47cb2d Suppress statement not reached warnings under Solaris Studio
168
195
  12957 306f0f3 afs: squash empty declaration warning
169
196
  12955 e006609 libafs: git ignore build artifacts on Solaris
170
197
 
171
198
  Show gerrit numbers by a revision in the local git repo::
172
199
 
173
- $ git gerrit-log openafs-stable-1_8_0
200
+ $ git gerrit-log -n3 openafs-stable-1_8_0
174
201
  12953 a08327f Update NEWS for 1.8.0 final release
175
202
  12938 acb0e84 afs_pioctl: avoid -Wpointer-sign
176
203
  12950 b73863b LINUX: fix RedHat 7.5 ENOTDIR issues
@@ -184,7 +211,7 @@ Show gerrit numbers by a range of revisions in the local git repo::
184
211
 
185
212
  Show just the gerrit numbers and subjects in the local git repo::
186
213
 
187
- $ git gerrit-log --format='{number}: {subject}'
214
+ $ git gerrit-log -n3 --format='{number}: {subject}'
188
215
  12958: Suppress statement not reached warnings under Solaris Studio
189
216
  12957: afs: squash empty declaration warning
190
217
  12955: libafs: git ignore build artifacts on Solaris
@@ -19,6 +19,7 @@ setup(
19
19
  version=get_version(),
20
20
  description='Gerrit review system command line tools.',
21
21
  long_description=open('README.rst').read(),
22
+ long_description_content_type='text/x-rst',
22
23
  author='Michael Meffie',
23
24
  author_email='mmeffie@sinenomine.net',
24
25
  license='BSD-2-Clause',
@@ -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.splitlines()
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 == "I68fd140aab7e65bec1ac537d19de89f9d32443c1"
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