diff --git a/.basedpyright/baseline.json b/.basedpyright/baseline.json index 14bf1024e..f51f7ce13 100644 --- a/.basedpyright/baseline.json +++ b/.basedpyright/baseline.json @@ -1,966 +1,470 @@ { - "files": { - "./git/cmd.py": [ - { - "code": "reportGeneralTypeIssues", - "range": { - "startColumn": 13, - "endColumn": 32, - "lineCount": 1 - } - }, - { - "code": "reportOptionalOperand", - "range": { - "startColumn": 27, - "endColumn": 35, - "lineCount": 1 - } - }, - { - "code": "reportReturnType", - "range": { - "startColumn": 28, - "endColumn": 31, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 28, - "endColumn": 40, - "lineCount": 1 - } - }, - { - "code": "reportReturnType", - "range": { - "startColumn": 19, - "endColumn": 68, - "lineCount": 1 - } - }, - { - "code": "reportReturnType", - "range": { - "startColumn": 19, - "endColumn": 31, - "lineCount": 1 - } - } - ], - "./git/config.py": [ - { - "code": "reportGeneralTypeIssues", - "range": { - "startColumn": 11, - "endColumn": 26, - "lineCount": 1 - } - }, - { - "code": "reportInvalidTypeVarUse", - "range": { - "startColumn": 43, - "endColumn": 45, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 41, - "endColumn": 48, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 32, - "endColumn": 39, - "lineCount": 1 - } - }, - { - "code": "reportCallIssue", - "range": { - "startColumn": 25, - "endColumn": 46, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 30, - "endColumn": 39, - "lineCount": 1 - } - } - ], - "./git/db.py": [ - { - "code": "reportIncompatibleMethodOverride", - "range": { - "startColumn": 8, - "endColumn": 12, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 61, - "endColumn": 79, - "lineCount": 1 - } - }, - { - "code": "reportIncompatibleMethodOverride", - "range": { - "startColumn": 8, - "endColumn": 14, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 70, - "endColumn": 88, - "lineCount": 1 - } - } - ], - "./git/index/base.py": [ - { - "code": "reportArgumentType", - "range": { - "startColumn": 30, - "endColumn": 36, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 28, - "endColumn": 34, - "lineCount": 1 - } - }, - { - "code": "reportAssignmentType", - "range": { - "startColumn": 38, - "endColumn": 76, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 45, - "endColumn": 53, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 60, - "endColumn": 63, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 56, - "endColumn": 60, - "lineCount": 1 - } - }, - { - "code": "reportSelfClsParameterName", - "range": { - "startColumn": 30, - "endColumn": 33, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 51, - "endColumn": 57, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 55, - "endColumn": 61, - "lineCount": 1 - } - }, - { - "code": "reportIncompatibleMethodOverride", - "range": { - "startColumn": 8, - "endColumn": 12, - "lineCount": 1 - } - }, - { - "code": "reportAssignmentType", - "range": { - "startColumn": 20, - "endColumn": 46, - "lineCount": 1 - } - } - ], - "./git/index/fun.py": [ - { - "code": "reportArgumentType", - "range": { - "startColumn": 8, - "endColumn": 36, - "lineCount": 1 - } - } - ], - "./git/index/typ.py": [ - { - "code": "reportArgumentType", - "range": { - "startColumn": 37, - "endColumn": 46, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 37, - "endColumn": 46, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 37, - "endColumn": 46, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 37, - "endColumn": 46, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 37, - "endColumn": 46, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 37, - "endColumn": 46, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 37, - "endColumn": 46, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 37, - "endColumn": 46, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 37, - "endColumn": 46, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 37, - "endColumn": 46, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 37, - "endColumn": 46, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 37, - "endColumn": 46, - "lineCount": 1 - } - } - ], - "./git/objects/base.py": [ - { - "code": "reportArgumentType", - "range": { - "startColumn": 20, - "endColumn": 27, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 29, - "endColumn": 36, - "lineCount": 1 - } - } - ], - "./git/objects/blob.py": [ - { - "code": "reportIncompatibleVariableOverride", - "range": { - "startColumn": 4, - "endColumn": 8, - "lineCount": 1 - } - } - ], - "./git/objects/commit.py": [ - { - "code": "reportIncompatibleVariableOverride", - "range": { - "startColumn": 4, - "endColumn": 8, - "lineCount": 1 - } - }, - { - "code": "reportIncompatibleMethodOverride", - "range": { - "startColumn": 8, - "endColumn": 31, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 20, - "endColumn": 33, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 20, - "endColumn": 30, - "lineCount": 1 - } - } - ], - "./git/objects/submodule/base.py": [ - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 19, - "endColumn": 24, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 19, - "endColumn": 24, - "lineCount": 1 - } - }, - { - "code": "reportReturnType", - "range": { - "startColumn": 23, - "endColumn": 25, - "lineCount": 1 - } - }, - { - "code": "reportReturnType", - "range": { - "startColumn": 23, - "endColumn": 25, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 41, - "endColumn": 46, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 52, - "endColumn": 84, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 20, - "endColumn": 40, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 35, - "endColumn": 42, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 15, - "endColumn": 20, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 15, - "endColumn": 20, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 19, - "endColumn": 33, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 19, - "endColumn": 33, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 15, - "endColumn": 27, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 15, - "endColumn": 27, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 15, - "endColumn": 19, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 15, - "endColumn": 19, - "lineCount": 1 - } - }, - { - "code": "reportReturnType", - "range": { - "startColumn": 18, - "endColumn": 20, - "lineCount": 1 - } - } - ], - "./git/objects/tag.py": [ - { - "code": "reportIncompatibleVariableOverride", - "range": { - "startColumn": 4, - "endColumn": 8, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 72, - "endColumn": 78, - "lineCount": 1 - } - } - ], - "./git/objects/tree.py": [ - { - "code": "reportIncompatibleVariableOverride", - "range": { - "startColumn": 4, - "endColumn": 8, - "lineCount": 1 - } - }, - { - "code": "reportIncompatibleMethodOverride", - "range": { - "startColumn": 8, - "endColumn": 31, - "lineCount": 1 - } - }, - { - "code": "reportReturnType", - "range": { - "startColumn": 19, - "endColumn": 83, - "lineCount": 1 - } - }, - { - "code": "reportReturnType", - "range": { - "startColumn": 15, - "endColumn": 54, - "lineCount": 1 - } - } - ], - "./git/objects/util.py": [ - { - "code": "reportAssignmentType", - "range": { - "startColumn": 23, - "endColumn": 34, - "lineCount": 1 - } - }, - { - "code": "reportReturnType", - "range": { - "startColumn": 22, - "endColumn": 26, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 30, - "endColumn": 34, - "lineCount": 1 - } - }, - { - "code": "reportReturnType", - "range": { - "startColumn": 15, - "endColumn": 54, - "lineCount": 1 - } - } - ], - "./git/refs/log.py": [ - { - "code": "reportArgumentType", - "range": { - "startColumn": 30, - "endColumn": 34, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 17, - "endColumn": 22, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 28, - "endColumn": 30, - "lineCount": 1 - } - } - ], - "./git/refs/reference.py": [ - { - "code": "reportInvalidTypeVarUse", - "range": { - "startColumn": 22, - "endColumn": 34, - "lineCount": 1 - } - }, - { - "code": "reportIncompatibleVariableOverride", - "range": { - "startColumn": 13, - "endColumn": 17, - "lineCount": 1 - } - } - ], - "./git/refs/symbolic.py": [ - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 15, - "endColumn": 20, - "lineCount": 1 - } - } - ], - "./git/refs/tag.py": [ - { - "code": "reportIncompatibleMethodOverride", - "range": { - "startColumn": 8, - "endColumn": 14, - "lineCount": 1 - } - }, - { - "code": "reportIncompatibleMethodOverride", - "range": { - "startColumn": 8, - "endColumn": 14, - "lineCount": 1 - } - } - ], - "./git/remote.py": [ - { - "code": "reportArgumentType", - "range": { - "startColumn": 25, - "endColumn": 36, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 29, - "endColumn": 40, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 26, - "endColumn": 38, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 26, - "endColumn": 38, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 26, - "endColumn": 38, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 26, - "endColumn": 38, - "lineCount": 1 - } - } - ], - "./git/repo/base.py": [ - { - "code": "reportRedeclaration", - "range": { - "startColumn": 4, - "endColumn": 15, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 18, - "endColumn": 22, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 35, - "endColumn": 41, - "lineCount": 1 - } - }, - { - "code": "reportReturnType", - "range": { - "startColumn": 15, - "endColumn": 46, - "lineCount": 1 - } - }, - { - "code": "reportReturnType", - "range": { - "startColumn": 15, - "endColumn": 51, - "lineCount": 1 - } - }, - { - "code": "reportReturnType", - "range": { - "startColumn": 15, - "endColumn": 28, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 16, - "endColumn": 31, - "lineCount": 1 - } - }, - { - "code": "reportTypedDictNotRequiredAccess", - "range": { - "startColumn": 21, - "endColumn": 31, - "lineCount": 1 - } - }, - { - "code": "reportTypedDictNotRequiredAccess", - "range": { - "startColumn": 34, - "endColumn": 44, - "lineCount": 1 - } - }, - { - "code": "reportTypedDictNotRequiredAccess", - "range": { - "startColumn": 65, - "endColumn": 79, - "lineCount": 1 - } - }, - { - "code": "reportTypedDictNotRequiredAccess", - "range": { - "startColumn": 82, - "endColumn": 102, - "lineCount": 1 - } - }, - { - "code": "reportTypedDictNotRequiredAccess", - "range": { - "startColumn": 50, - "endColumn": 69, - "lineCount": 1 - } - }, - { - "code": "reportTypedDictNotRequiredAccess", - "range": { - "startColumn": 68, - "endColumn": 85, - "lineCount": 1 - } - }, - { - "code": "reportTypedDictNotRequiredAccess", - "range": { - "startColumn": 88, - "endColumn": 111, - "lineCount": 1 - } - }, - { - "code": "reportTypedDictNotRequiredAccess", - "range": { - "startColumn": 51, - "endColumn": 73, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 12, - "endColumn": 26, - "lineCount": 1 - } - }, - { - "code": "reportAssignmentType", - "range": { - "startColumn": 22, - "endColumn": 38, - "lineCount": 1 - } - } - ], - "./git/repo/fun.py": [ - { - "code": "reportArgumentType", - "range": { - "startColumn": 25, - "endColumn": 33, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 35, - "endColumn": 43, - "lineCount": 1 - } - }, - { - "code": "reportAssignmentType", - "range": { - "startColumn": 18, - "endColumn": 28, - "lineCount": 1 - } - }, - { - "code": "reportReturnType", - "range": { - "startColumn": 11, - "endColumn": 14, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 24, - "endColumn": 27, - "lineCount": 1 - } - }, - { - "code": "reportReturnType", - "range": { - "startColumn": 11, - "endColumn": 14, - "lineCount": 1 - } - }, - { - "code": "reportReturnType", - "range": { - "startColumn": 11, - "endColumn": 20, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 25, - "endColumn": 28, - "lineCount": 1 - } - }, - { - "code": "reportArgumentType", - "range": { - "startColumn": 24, - "endColumn": 27, - "lineCount": 1 - } - } - ], - "./test/deprecation/test_basic.py": [ - { - "code": "reportUnusedExpression", - "range": { - "startColumn": 12, - "endColumn": 62, - "lineCount": 1 - } - } - ] - } + "files": { + "./git/config.py": [ + { + "code": "reportInvalidTypeVarUse", + "range": { + "startColumn": 43, + "endColumn": 45, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 41, + "endColumn": 48, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 32, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportCallIssue", + "range": { + "startColumn": 25, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 30, + "endColumn": 39, + "lineCount": 1 + } + } + ], + "./git/db.py": [ + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 12, + "lineCount": 1 + } + }, + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 14, + "lineCount": 1 + } + } + ], + "./git/index/base.py": [ + { + "code": "reportArgumentType", + "range": { + "startColumn": 30, + "endColumn": 36, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 28, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 51, + "endColumn": 57, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 55, + "endColumn": 61, + "lineCount": 1 + } + }, + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 12, + "lineCount": 1 + } + }, + { + "code": "reportAssignmentType", + "range": { + "startColumn": 20, + "endColumn": 46, + "lineCount": 1 + } + } + ], + "./git/objects/blob.py": [ + { + "code": "reportIncompatibleVariableOverride", + "range": { + "startColumn": 4, + "endColumn": 8, + "lineCount": 1 + } + } + ], + "./git/objects/commit.py": [ + { + "code": "reportIncompatibleVariableOverride", + "range": { + "startColumn": 4, + "endColumn": 8, + "lineCount": 1 + } + }, + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 20, + "endColumn": 33, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 20, + "endColumn": 30, + "lineCount": 1 + } + } + ], + "./git/objects/submodule/base.py": [ + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 19, + "endColumn": 24, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 19, + "endColumn": 24, + "lineCount": 1 + } + }, + { + "code": "reportReturnType", + "range": { + "startColumn": 23, + "endColumn": 25, + "lineCount": 1 + } + }, + { + "code": "reportReturnType", + "range": { + "startColumn": 23, + "endColumn": 25, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 52, + "endColumn": 84, + "lineCount": 1 + } + } + ], + "./git/objects/tag.py": [ + { + "code": "reportIncompatibleVariableOverride", + "range": { + "startColumn": 4, + "endColumn": 8, + "lineCount": 1 + } + } + ], + "./git/objects/tree.py": [ + { + "code": "reportIncompatibleVariableOverride", + "range": { + "startColumn": 4, + "endColumn": 8, + "lineCount": 1 + } + }, + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportReturnType", + "range": { + "startColumn": 19, + "endColumn": 83, + "lineCount": 1 + } + }, + { + "code": "reportReturnType", + "range": { + "startColumn": 15, + "endColumn": 54, + "lineCount": 1 + } + } + ], + "./git/objects/util.py": [ + { + "code": "reportAssignmentType", + "range": { + "startColumn": 23, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportReturnType", + "range": { + "startColumn": 22, + "endColumn": 26, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 30, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportReturnType", + "range": { + "startColumn": 15, + "endColumn": 54, + "lineCount": 1 + } + } + ], + "./git/refs/log.py": [ + { + "code": "reportArgumentType", + "range": { + "startColumn": 30, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 17, + "endColumn": 22, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 28, + "endColumn": 30, + "lineCount": 1 + } + } + ], + "./git/refs/reference.py": [ + { + "code": "reportIncompatibleVariableOverride", + "range": { + "startColumn": 13, + "endColumn": 17, + "lineCount": 1 + } + } + ], + "./git/refs/symbolic.py": [ + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 15, + "endColumn": 20, + "lineCount": 1 + } + } + ], + "./git/refs/tag.py": [ + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 14, + "lineCount": 1 + } + }, + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 14, + "lineCount": 1 + } + } + ], + "./git/remote.py": [ + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 26, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 26, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 26, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 26, + "endColumn": 38, + "lineCount": 1 + } + } + ], + "./git/repo/base.py": [ + { + "code": "reportReturnType", + "range": { + "startColumn": 15, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportTypedDictNotRequiredAccess", + "range": { + "startColumn": 21, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportTypedDictNotRequiredAccess", + "range": { + "startColumn": 34, + "endColumn": 44, + "lineCount": 1 + } + }, + { + "code": "reportTypedDictNotRequiredAccess", + "range": { + "startColumn": 65, + "endColumn": 79, + "lineCount": 1 + } + }, + { + "code": "reportTypedDictNotRequiredAccess", + "range": { + "startColumn": 82, + "endColumn": 102, + "lineCount": 1 + } + }, + { + "code": "reportTypedDictNotRequiredAccess", + "range": { + "startColumn": 50, + "endColumn": 69, + "lineCount": 1 + } + }, + { + "code": "reportTypedDictNotRequiredAccess", + "range": { + "startColumn": 68, + "endColumn": 85, + "lineCount": 1 + } + }, + { + "code": "reportTypedDictNotRequiredAccess", + "range": { + "startColumn": 88, + "endColumn": 111, + "lineCount": 1 + } + }, + { + "code": "reportTypedDictNotRequiredAccess", + "range": { + "startColumn": 51, + "endColumn": 73, + "lineCount": 1 + } + } + ], + "./git/repo/fun.py": [ + { + "code": "reportReturnType", + "range": { + "startColumn": 11, + "endColumn": 20, + "lineCount": 1 + } + } + ], + "./test/deprecation/test_basic.py": [ + { + "code": "reportUnusedExpression", + "range": { + "startColumn": 12, + "endColumn": 62, + "lineCount": 1 + } + } + ] + } } diff --git a/.github/workflows/alpine-test.yml b/.github/workflows/alpine-test.yml index b10336a15..d87fdd079 100644 --- a/.github/workflows/alpine-test.yml +++ b/.github/workflows/alpine-test.yml @@ -89,7 +89,7 @@ jobs: command -v git python git version python --version - python -c 'import os, sys; print(f"sys.platform={sys.platform!r}, os.name={os.name!r}")' + python -c 'import os, sys; print(f"{sys.platform=}, {os.name=}")' - name: Test with pytest run: | diff --git a/.github/workflows/cygwin-test.yml b/.github/workflows/cygwin-test.yml index e14856e7e..4f7032f31 100644 --- a/.github/workflows/cygwin-test.yml +++ b/.github/workflows/cygwin-test.yml @@ -133,7 +133,7 @@ jobs: command -v git python git version python --version - python -c 'import os, sys; print(f"sys.platform={sys.platform!r}, os.name={os.name!r}")' + python -c 'import os, sys; print(f"{sys.platform=}, {os.name=}")' - name: Test with pytest (${{ matrix.additional-pytest-args }}) run: | diff --git a/.github/workflows/dependencies.yml b/.github/workflows/dependencies.yml index 4803e733b..b4046dd9b 100644 --- a/.github/workflows/dependencies.yml +++ b/.github/workflows/dependencies.yml @@ -11,12 +11,12 @@ permissions: jobs: test: - runs-on: ${{ matrix.python-version == '3.7' && 'ubuntu-22.04' || 'ubuntu-latest' }} + runs-on: ubuntu-latest strategy: fail-fast: false matrix: project: [smmap, gitdb] - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.13t"] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.13t"] steps: - uses: actions/checkout@v7 @@ -40,3 +40,18 @@ jobs: else pytest -o addopts= -v smmap/smmap/test fi + uv-sync: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + with: + persist-credentials: false + + - name: Install uv + uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0 + with: + enable-cache: false + version: "0.12.13" # Released on 2026-09-10 + + # Ensure that dependencies and dependency groups are consistent and resolvable. + - run: uv sync --all-extras --all-groups --dry-run diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml index 7dfa2e8f2..3fbbe2ec6 100644 --- a/.github/workflows/pythonpackage.yml +++ b/.github/workflows/pythonpackage.yml @@ -17,22 +17,14 @@ jobs: strategy: matrix: os-type: [ubuntu, macos, windows] - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14", "3.14t", "3.15", "3.15t"] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14", "3.14t", "3.15", "3.15t"] exclude: - - os-type: macos - python-version: "3.7" # Not available for the ARM-based macOS runners. - os-type: macos python-version: "3.14t" - os-type: macos python-version: "3.15t" include: - - os-ver: latest - - os-type: ubuntu - python-version: "3.7" - os-ver: "22.04" - build-docs: true # We ensure documentation builds, except on very old interpreters. - - python-version: "3.7" - build-docs: false - python-version: "3.8" build-docs: false - experimental: false @@ -43,7 +35,7 @@ jobs: fail-fast: false - runs-on: ${{ matrix.os-type }}-${{ matrix.os-ver }} + runs-on: ${{ matrix.os-type }}-latest defaults: run: @@ -140,7 +132,7 @@ jobs: command -v git python git version python --version - python -c 'import os, sys; print(f"sys.platform={sys.platform!r}, os.name={os.name!r}")' + python -c 'import os, sys; print(f"{sys.platform=}, {os.name=}")' # For debugging hook tests on native Windows systems that may have WSL. - name: Show bash.exe candidates (Windows) @@ -152,7 +144,7 @@ jobs: continue-on-error: true - name: Check types with mypy - if: matrix.python-version != '3.7' && matrix.python-version != '3.8' + if: matrix.python-version != '3.8' run: | mypy --python-version="${PYTHON_VERSION%t}" # Version only, with no "t" for free-threaded. env: @@ -173,5 +165,5 @@ jobs: - name: Documentation if: matrix.build-docs run: | - pip install '.[doc]' + pip install --group doc make -C doc html diff --git a/.gitignore b/.gitignore index eab294a65..79e543121 100644 --- a/.gitignore +++ b/.gitignore @@ -52,3 +52,6 @@ output.txt # Files created by OSS-Fuzz when running locally fuzz_*.pkg.spec + +# Lock files created by uv, pixi, pipenv and others +*.lock diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index dfa5fc91e..45115f60c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,13 +1,6 @@ exclude: ^(?:gitdb|smmap)/ repos: -- repo: https://github.com/codespell-project/codespell - rev: v2.4.3 - hooks: - - id: codespell - additional_dependencies: [tomli] - exclude: ^test/fixtures/ - - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.16.5 hooks: diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 9bce80fd2..206d3f492 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -13,6 +13,10 @@ build: # nodejs: "20" # rust: "1.70" # golang: "1.20" + jobs: + install: + - python -m pip install --upgrade --no-cache-dir pip setuptools + - python -m pip install --upgrade --upgrade-strategy only-if-needed --no-cache-dir --group doc . # Build documentation in the "doc/" directory with Sphinx. sphinx: @@ -24,13 +28,3 @@ sphinx: # Optionally build your docs in additional formats such as PDF and ePub. formats: all - -# Optional but recommended, declare the Python requirements required -# to build your documentation. -# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html -python: - install: - - method: pip - path: . - extra_requirements: - - doc diff --git a/AGENTS.md b/AGENTS.md index ee4ef9d95..6b2963cf4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,7 +1,42 @@ +# Contribution guidelines + +Before starting work, read and follow [CONTRIBUTING.md](CONTRIBUTING.md), +including the [Prevent agent impersonation](CONTRIBUTING.md#prevent-agent-impersonation) +section governing identification when communicating through a person's account. + # Commit messages -Every commit must have a descriptive title and a substantive body. Title-only -commit messages are not acceptable. +Follow Conventional Commits for every commit. Every commit must have a +descriptive title and a substantive body. Title-only commit messages are not +acceptable. + +## Formatting + +Write commit messages in Markdown and assume readers view them with syntax +highlighting. Enclose code identifiers, package and module names, file paths, +and shell commands in backticks. Use Markdown whenever it helps readers +understand or navigate the prose. + +## Titles + +- Every title must use the form `type: description` or + `type(scope): description`. +- Use `feat:` for user-visible features and `fix:` for user-visible fixes. +- Use appropriate prefixes for other changes, such as `docs:`, `test:`, `ci:`, + `build:`, `refactor:`, `perf:`, `style:`, or `chore:`. +- Breaking changes must use `!` immediately before the colon, for example + `feat!:`, `refactor!:`, or a scoped form such as `fix(repo)!:`. +- Optionally scope a commit to the affected component, for example `fix(repo):`. + +Example titles: + +- `feat: add support for a new Git option` +- `fix(repo): handle bare repositories correctly` +- `build!: drop support for an older Python version` +- `ci: add an independent documentation build` +- `refactor(repo): simplify repository initialization` + +## Body The body must explain the problem or motivation, what changed, and why the chosen approach addresses it. Include relevant behavior before and after the diff --git a/README.md b/README.md index c5a069bfe..029b4fa00 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ GitPython needs the `git` executable to be installed on the system and available by setting the `GIT_PYTHON_GIT_EXECUTABLE=` environment variable. - Git (1.7.x or newer) -- Python >= 3.7 +- Python >= 3.8 The list of dependencies are listed in [`./requirements.txt`](https://github.com/gitpython-developers/GitPython/blob/main/requirements.txt) and [`./test-requirements.txt`](https://github.com/gitpython-developers/GitPython/blob/main/test-requirements.txt). The installer takes care of installing them for you. diff --git a/VERSION b/VERSION index 4eb2ee669..944880fa1 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.1.62 +3.2.0 diff --git a/doc/requirements.txt b/doc/requirements.txt deleted file mode 100644 index 24472ba39..000000000 --- a/doc/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -sphinx >= 7.4.7, < 8 -sphinx_rtd_theme -sphinx-autodoc-typehints diff --git a/doc/source/changes.rst b/doc/source/changes.rst index 20bfaeae6..2f537c99d 100644 --- a/doc/source/changes.rst +++ b/doc/source/changes.rst @@ -2,6 +2,31 @@ Changelog ========= +3.2.0 +===== + +Drops support for Python 3.7 (5 years after its EOL). + +Security fixes for + +* Deprecate the pure-Python ``GitDB`` object database backend due to security and + performance issues. Selecting it or a subclass through ``odbt`` now emits a + ``DeprecationWarning``. Remove ``odbt=GitDB`` to use ``GitCmdObjectDB``, the + existing default, or select ``odbt=GitCmdObjectDB`` explicitly. The ``gitdb`` + package remains a dependency for shared types and utilities. +* https://github.com/gitpython-developers/GitPython/security/advisories/GHSA-gq48-pqfc-9p58 +* https://github.com/gitpython-developers/GitPython/security/advisories/GHSA-23mf-xhv8-69c2 +* https://github.com/gitpython-developers/GitPython/security/advisories/GHSA-f9j4-qggq-h239 +* https://github.com/gitpython-developers/GitPython/security/advisories/GHSA-w8jc-g24h-crhw +* https://github.com/gitpython-developers/GitPython/security/advisories/GHSA-m64x-33q8-m5h7 +* https://github.com/gitpython-developers/GitPython/security/advisories/GHSA-fx3j-rwgx-fr94 + +If you can, also try and provide feedback on the upcoming v4 branch +https://github.com/gitpython-developers/GitPython/pull/2177 - patches welcome. + +See the following for all changes. +https://github.com/gitpython-developers/GitPython/releases/tag/3.2.0 + 3.1.62 ====== diff --git a/doc/source/intro.rst b/doc/source/intro.rst index e1075b1c9..dec2ec0fa 100644 --- a/doc/source/intro.rst +++ b/doc/source/intro.rst @@ -13,7 +13,7 @@ The object database implementation is optimized for handling large quantities of Requirements ============ -* `Python`_ >= 3.7 +* `Python`_ >= 3.8 * `Git`_ 1.7.0 or newer It should also work with older versions, but it may be that some operations involving remotes will not work as expected. diff --git a/doc/source/tutorial.rst b/doc/source/tutorial.rst index d095d3be3..a1ac1eace 100644 --- a/doc/source/tutorial.rst +++ b/doc/source/tutorial.rst @@ -78,6 +78,12 @@ Query relevant repository paths ... :class:`Heads ` Heads are branches in git-speak. :class:`References ` are pointers to a specific commit or to other references. Heads and :class:`Tags ` are a kind of references. GitPython allows you to query them rather intuitively. +To obtain the current commit ID, use ``repo.head.commit.hexsha``. This works both +on a branch and with a detached HEAD, provided HEAD resolves to an existing commit. +When ``repo.head.is_detached`` is true, HEAD points directly to a commit and there +is no active branch: reading ``repo.head.reference`` or ``repo.active_branch`` +raises :exc:`TypeError`. The branch examples below assume an attached HEAD. + .. literalinclude:: ../../test/test_docs.py :language: python :dedent: 8 @@ -152,7 +158,7 @@ Examining References :start-after: # [2-test_references_and_objects] :end-before: # ![2-test_references_and_objects] -A :class:`symbolic reference ` is a special case of a reference as it points to another reference instead of a commit. +A :class:`symbolic reference ` can point to another reference. When detached, it points directly to a commit instead. Reading its ``commit`` property resolves the commit in either state. Assigning a commit to ``reference`` detaches it; reading ``reference`` then raises :exc:`TypeError`. .. literalinclude:: ../../test/test_docs.py :language: python @@ -507,24 +513,29 @@ Object Databases The type of the database determines certain performance characteristics, such as the quantity of objects that can be read per second, the resource usage when reading large data files, as well as the average memory footprint of your application. -GitDB -===== -The GitDB is a pure-python implementation of the git object database. It is the default database to use in GitPython 0.3. It uses less memory when handling huge files, but will be 2 to 5 times slower when extracting large quantities of small objects from densely packed repositories:: +GitCmdObjectDB +============== +``GitCmdObjectDB`` is the default and recommended backend. It reads objects and +resolves abbreviated object IDs through persistent ``git cat-file`` processes:: - repo = Repo("path/to/repo", odbt=GitDB) + repo = Repo("path/to/repo") + # Equivalent explicit selection: + repo = Repo("path/to/repo", odbt=GitCmdObjectDB) +GitDB +===== .. warning:: - ``GitDB`` may fail or become extremely slow when traversing trees in - repositories with very large commits (thousands of changed files in a - single commit). If you encounter ``RecursionError`` or excessive - slowness during tree traversal, switch to ``GitCmdObjectDB`` instead. - + The pure-Python ``GitDB`` backend is deprecated due to security and performance + issues. Its object parsers can exhaust resources or return incorrect object + data when processing untrusted repositories. Do not use it for untrusted data. -GitCmdObjectDB -============== -The git command database uses persistent git-cat-file instances to read repository information. These operate very fast under all conditions, but will consume additional memory for the process itself. When extracting large files, memory usage will be much higher than ``GitDB``:: +Selecting ``odbt=GitDB`` (including a subclass) emits a ``DeprecationWarning``. +To migrate, remove ``odbt=GitDB`` or replace it with ``odbt=GitCmdObjectDB`` when +opening, initializing, or cloning a repository. The deprecated backend remains +available for compatibility; deprecation does not fix its parsing issues. - repo = Repo("path/to/repo", odbt=GitCmdObjectDB) +The ``gitdb`` package remains a dependency because GitPython still uses its shared +types and utilities. Git Command Debugging and Customization *************************************** diff --git a/fuzzing/fuzz-targets/utils.py b/fuzzing/fuzz-targets/utils.py index 97e6eab98..34b05f7eb 100644 --- a/fuzzing/fuzz-targets/utils.py +++ b/fuzzing/fuzz-targets/utils.py @@ -49,7 +49,7 @@ def read_lines_from_file(file_path: str) -> list: except FileNotFoundError: print(f"File not found: {file_path}") return [] - except IOError as e: + except OSError as e: print(f"Error reading file {file_path}: {e}") return [] diff --git a/git/__init__.py b/git/__init__.py index 1b2360e3a..ecc6cd94e 100644 --- a/git/__init__.py +++ b/git/__init__.py @@ -295,6 +295,6 @@ def refresh(path: Optional[PathLike] = None) -> None: try: refresh() except Exception as _exc: - raise ImportError("Failed to initialize: {0}".format(_exc)) from _exc + raise ImportError(f"Failed to initialize: {_exc}") from _exc # } END initialize git executable path diff --git a/git/cmd.py b/git/cmd.py index 193dfd4f6..cb88e7c24 100644 --- a/git/cmd.py +++ b/git/cmd.py @@ -100,7 +100,7 @@ def handle_process_output( - process: "Git.AutoInterrupt" | Popen, + process: Union["Git.AutoInterrupt", Popen], stdout_handler: Union[ None, Callable[[AnyStr], None], @@ -395,9 +395,7 @@ def wait(self, stderr: Union[None, str, bytes] = b"") -> int: :raise git.exc.GitCommandError: If the return status is not 0. """ - if stderr is None: - stderr_b = b"" - stderr_b = force_bytes(data=stderr, encoding="utf-8") + stderr_b = force_bytes(data=stderr, encoding="utf-8") or b"" status: Union[int, None] if self.proc is not None: status = self.proc.wait() @@ -647,7 +645,9 @@ class Git(metaclass=_GitMeta): "_version_info_token", ) - re_unsafe_protocol = re.compile(r"(.+)::.+") + # Match Git's leading transport selector, including an empty helper name. + # Git also selects the command-executing ext helper for an ext:// URL. + re_unsafe_protocol = re.compile(r"([A-Za-z0-9][A-Za-z0-9+.-]*|)::|ext://") unsafe_git_ls_remote_options = [ # This option allows arbitrary command execution in git-ls-remote. @@ -947,7 +947,8 @@ def check_unsafe_protocols(cls, url: str) -> None: Apart from the usual protocols (http, git, ssh), Git allows "remote helpers" that have the form ``::
``. One of these helpers (``ext::``) - can be used to invoke any arbitrary command. + can be used to invoke any arbitrary command. Git also selects that helper + for ``ext://`` URLs and interprets the URL as a command path. See: @@ -956,11 +957,19 @@ def check_unsafe_protocols(cls, url: str) -> None: """ match = cls.re_unsafe_protocol.match(url) if match: - protocol = match.group(1) + protocol = match.group(0) raise UnsafeProtocolError( - f"The `{protocol}::` protocol looks suspicious, use `allow_unsafe_protocols=True` to allow it." + f"The `{protocol}` protocol looks suspicious, use `allow_unsafe_protocols=True` to allow it." ) + def _check_unsafe_protocols_in_args(self, args: Sequence[Any], kwargs: Mapping[str, Any]) -> None: + """Check positional operands and standalone values in rendered command options. + + A short flag's split value can become the repository operand before ``--``. + """ + for arg in self._unpack_args(args) + self.transform_kwargs(**kwargs): + self.check_unsafe_protocols(arg) + @classmethod def _canonicalize_option_name(cls, option: str) -> str: """Return the option name used for unsafe-option checks. @@ -1131,16 +1140,23 @@ def ls_remote( self, *args: Any, allow_unsafe_options: bool = False, + allow_unsafe_protocols: bool = False, **kwargs: Any, ) -> Union[str, bytes, Tuple[int, Union[str, bytes], str], "Git.AutoInterrupt"]: """List references in a remote repository. :param allow_unsafe_options: Allow unsafe options, like ``--upload-pack`` or ``--exec``. + + :param allow_unsafe_protocols: + Allow unsafe protocols to be used, like ``ext``. Positional arguments + and split short-option values are checked. """ if not allow_unsafe_options: candidate_options = self._option_candidates(args, kwargs) Git.check_unsafe_options(options=candidate_options, unsafe_options=self.unsafe_git_ls_remote_options) + if not allow_unsafe_protocols: + self._check_unsafe_protocols_in_args(args, kwargs) return self._call_process("ls_remote", *args, **kwargs) @property @@ -1180,52 +1196,112 @@ def version_info(self) -> Tuple[int, ...]: def execute( self, command: Union[str, Sequence[Any]], + istream: Union[None, int, BinaryIO] = None, *, as_process: Literal[True], + **subprocess_kwargs: Any, ) -> "AutoInterrupt": ... @overload def execute( self, command: Union[str, Sequence[Any]], + istream: Union[None, int, BinaryIO] = None, *, as_process: Literal[False] = False, - stdout_as_string: Literal[True], - ) -> Union[str, Tuple[int, str, str]]: ... + with_extended_output: Literal[False] = False, + stdout_as_string: Literal[True] = True, + with_stdout: Literal[True] = True, + **subprocess_kwargs: Any, + ) -> str: ... @overload def execute( self, command: Union[str, Sequence[Any]], + istream: Union[None, int, BinaryIO] = None, *, as_process: Literal[False] = False, - stdout_as_string: Literal[False] = False, - ) -> Union[bytes, Tuple[int, bytes, str]]: ... + with_extended_output: Literal[False] = False, + stdout_as_string: Literal[False], + universal_newlines: Literal[False] = False, + with_stdout: Literal[True] = True, + **subprocess_kwargs: Any, + ) -> bytes: ... @overload def execute( self, command: Union[str, Sequence[Any]], + istream: Union[None, int, BinaryIO] = None, *, - with_extended_output: Literal[False], - as_process: Literal[False], - stdout_as_string: Literal[True], - ) -> str: ... + as_process: Literal[False] = False, + with_extended_output: Literal[True], + stdout_as_string: Literal[True] = True, + with_stdout: Literal[True] = True, + **subprocess_kwargs: Any, + ) -> Tuple[int, str, str]: ... @overload def execute( self, command: Union[str, Sequence[Any]], + istream: Union[None, int, BinaryIO] = None, *, - with_extended_output: Literal[False], - as_process: Literal[False], + as_process: Literal[False] = False, + with_extended_output: Literal[True], stdout_as_string: Literal[False], - ) -> bytes: ... + universal_newlines: Literal[False] = False, + with_stdout: Literal[True] = True, + **subprocess_kwargs: Any, + ) -> Tuple[int, bytes, str]: ... + @overload def execute( self, command: Union[str, Sequence[Any]], - istream: Union[None, BinaryIO] = None, + istream: Union[None, int, BinaryIO] = None, + *, + as_process: Literal[False] = False, + with_extended_output: Literal[True], + **subprocess_kwargs: Any, + ) -> Tuple[int, Union[str, bytes, None], str]: ... + + @overload + def execute( + self, + command: Union[str, Sequence[Any]], + istream: Union[None, int, BinaryIO] = None, + *, + as_process: Literal[False] = False, + with_extended_output: Literal[False] = False, + **subprocess_kwargs: Any, + ) -> Union[str, bytes, None]: ... + + @overload + def execute( + self, + command: Union[str, Sequence[Any]], + istream: Union[None, int, BinaryIO] = None, + with_extended_output: bool = False, + with_exceptions: bool = True, + as_process: bool = False, + output_stream: Union[None, BinaryIO] = None, + stdout_as_string: bool = True, + kill_after_timeout: Union[None, float] = None, + with_stdout: bool = True, + universal_newlines: bool = False, + shell: Union[None, bool] = None, + env: Union[None, Mapping[str, str]] = None, + max_chunk_size: int = io.DEFAULT_BUFFER_SIZE, + strip_newline_in_stdout: bool = True, + **subprocess_kwargs: Any, + ) -> Union[None, str, bytes, Tuple[int, Union[str, bytes, None], str], AutoInterrupt]: ... + + def execute( + self, + command: Union[str, Sequence[Any]], + istream: Union[None, int, BinaryIO] = None, with_extended_output: bool = False, with_exceptions: bool = True, as_process: bool = False, @@ -1239,7 +1315,7 @@ def execute( max_chunk_size: int = io.DEFAULT_BUFFER_SIZE, strip_newline_in_stdout: bool = True, **subprocess_kwargs: Any, - ) -> Union[str, bytes, Tuple[int, Union[str, bytes], str], AutoInterrupt]: + ) -> Union[None, str, bytes, Tuple[int, Union[str, bytes, None], str], AutoInterrupt]: R"""Handle executing the command, and consume and return the returned information (stdout). @@ -1303,9 +1379,9 @@ def execute( carefully considered, due to the following limitations: 1. This feature is not supported at all on Windows. - 2. Effectiveness may vary by operating system. ``ps --ppid`` is used to - enumerate child processes, which is available on most GNU/Linux systems - but not most others. + 2. Enumerating child processes requires ``pgrep -P``, or a ``ps`` command + supporting the POSIX ``-A`` and ``-o`` options if ``pgrep`` is not + installed. Effectiveness may vary on systems without these commands. 3. Deeper descendants do not receive signals, though they may sometimes terminate as a consequence of their parent processes being killed. 4. `kill_after_timeout` uses ``SIGKILL``, which can have negative side @@ -1465,14 +1541,24 @@ def kill_process(pid: int) -> None: This callback implementation would be ineffective and unsafe on Windows. """ - p = Popen(["ps", "--ppid", str(pid)], stdout=PIPE) child_pids = [] - if p.stdout is not None: - for line in p.stdout: - if len(line.split()) > 0: - local_pid = (line.split())[0] - if local_pid.isdigit(): - child_pids.append(int(local_pid)) + try: + p = Popen(["pgrep", "-P", str(pid)], stdout=PIPE) + except FileNotFoundError: + # POSIX ps does not support selecting by parent PID. + with Popen(["ps", "-A", "-o", "pid=", "-o", "ppid="], stdout=PIPE) as p: + if p.stdout is not None: + for line in p.stdout: + fields = line.split() + if len(fields) == 2 and all(field.isdigit() for field in fields): + if int(fields[1]) == pid: + child_pids.append(int(fields[0])) + else: + with p: + if p.stdout is not None: + for line in p.stdout: + if line.strip().isdigit(): + child_pids.append(int(line)) try: os.kill(pid, signal.SIGKILL) for child_pid in child_pids: @@ -1493,7 +1579,7 @@ def make_timeout_error() -> Union[str, bytes]: err = f'Timeout: the command "{" ".join(redacted_command)}" did not complete in {timeout:g} secs.' return err if universal_newlines else err.encode(defenc) - def communicate() -> Tuple[AnyStr, AnyStr]: + def communicate() -> Tuple[Union[str, bytes, None], Union[str, bytes, None]]: assert watchdog is not None assert kill_check is not None watchdog.start() @@ -1513,8 +1599,8 @@ def communicate() -> Tuple[AnyStr, AnyStr]: # Wait for the process to return. status = 0 - stdout_value: Union[str, bytes] = b"" - stderr_value: Union[str, bytes] = b"" + stdout_value: Union[str, bytes, None] = b"" + stderr_value: Union[str, bytes, None] = b"" newline = "\n" if universal_newlines else b"\n" try: if output_stream is None: @@ -1556,7 +1642,7 @@ def communicate() -> Tuple[AnyStr, AnyStr]: if self.GIT_PYTHON_TRACE == "full": cmdstr = " ".join(redacted_command) - def as_text(stdout_value: Union[bytes, str]) -> str: + def as_text(stdout_value: Union[bytes, str, None]) -> str: return not output_stream and safe_decode(stdout_value) or "" # END as_text @@ -1581,6 +1667,8 @@ def as_text(stdout_value: Union[bytes, str]) -> str: if isinstance(stdout_value, bytes) and stdout_as_string: # Could also be output_stream. stdout_value = safe_decode(stdout_value) + # stderr is always captured through PIPE. + assert stderr_value is not None # Allow access to the command's status code. if with_extended_output: return (status, stdout_value, safe_decode(stderr_value)) @@ -1725,6 +1813,12 @@ def _call_process( This allows your commands to call git more conveniently, as ``None`` is realized as non-existent. + Positional arguments may intentionally contain command options. Higher-level + APIs must separate their operands with ``--`` where the Git command supports + it, or reject option-shaped operands where Git reparses them internally (for + example, ``pull`` and ``remote update``). Shell quoting cannot prevent Git + from interpreting a leading-dash argument as an option. + :param kwargs: Contains key-values for the following: @@ -1819,7 +1913,7 @@ def _parse_object_header(self, header_line: str) -> Tuple[str, str, int]: raise ValueError("Failed to parse header: %r" % header_line) return (tokens[0], tokens[1], int(tokens[2])) - def _prepare_ref(self, ref: AnyStr) -> bytes: + def _prepare_ref(self, ref: object) -> bytes: # Required for command to separate refs on stdin, as bytes. if isinstance(ref, bytes): # Assume 40 bytes hexsha - bin-to-ascii for some reason returns bytes, not text. @@ -1846,7 +1940,7 @@ def _get_persistent_cmd(self, attr_name: str, cmd_name: str, *args: Any, **kwarg cmd = cast("Git.AutoInterrupt", cmd) return cmd - def __get_object_header(self, cmd: "Git.AutoInterrupt", ref: AnyStr) -> Tuple[str, str, int]: + def __get_object_header(self, cmd: "Git.AutoInterrupt", ref: Union[str, bytes]) -> Tuple[str, str, int]: if cmd.stdin and cmd.stdout: cmd.stdin.write(self._prepare_ref(ref)) cmd.stdin.flush() @@ -1854,7 +1948,7 @@ def __get_object_header(self, cmd: "Git.AutoInterrupt", ref: AnyStr) -> Tuple[st else: raise ValueError("cmd stdin was empty") - def get_object_header(self, ref: str) -> Tuple[str, str, int]: + def get_object_header(self, ref: Union[str, bytes]) -> Tuple[str, str, int]: """Use this method to quickly examine the type and size of the object behind the given ref. @@ -1868,7 +1962,7 @@ def get_object_header(self, ref: str) -> Tuple[str, str, int]: cmd = self._get_persistent_cmd("cat_file_header", "cat_file", batch_check=True) return self.__get_object_header(cmd, ref) - def get_object_data(self, ref: str) -> Tuple[str, str, int, bytes]: + def get_object_data(self, ref: Union[str, bytes]) -> Tuple[str, str, int, bytes]: """Similar to :meth:`get_object_header`, but returns object data as well. :return: @@ -1882,7 +1976,7 @@ def get_object_data(self, ref: str) -> Tuple[str, str, int, bytes]: del stream return (hexsha, typename, size, data) - def stream_object_data(self, ref: str) -> Tuple[str, str, int, "Git.CatFileContentStream"]: + def stream_object_data(self, ref: Union[str, bytes]) -> Tuple[str, str, int, "Git.CatFileContentStream"]: """Similar to :meth:`get_object_data`, but returns the data as a stream. :return: diff --git a/git/config.py b/git/config.py index aef881d2e..a3437d525 100644 --- a/git/config.py +++ b/git/config.py @@ -10,36 +10,35 @@ import abc import configparser as cp import fnmatch -from functools import wraps import inspect -from io import BufferedReader, IOBase import logging import os import os.path as osp import re import sys - -from git.compat import defenc, force_text -from git.util import LockFile +from functools import wraps +from io import BufferedReader, IOBase # typing------------------------------------------------------- - from typing import ( + IO, + TYPE_CHECKING, Any, Callable, + Dict, Generic, - IO, List, - Dict, + OrderedDict, Sequence, - TYPE_CHECKING, Tuple, TypeVar, Union, cast, ) -from git.types import Lit_config_levels, ConfigLevels_Tup, PathLike, assert_never, _T +from git.compat import defenc, force_text +from git.types import _T, ConfigLevels_Tup, Lit_config_levels, PathLike, assert_never +from git.util import LockFile if TYPE_CHECKING: from io import BytesIO @@ -47,17 +46,9 @@ from git.repo.base import Repo T_ConfigParser = TypeVar("T_ConfigParser", bound="GitConfigParser") -T_OMD_value = TypeVar("T_OMD_value", str, bytes, int, float, bool) - -if sys.version_info[:3] < (3, 7, 2): - # typing.Ordereddict not added until Python 3.7.2. - from collections import OrderedDict +T_OMD_value = TypeVar("T_OMD_value", str, bytes, int, float, bool, None) - OrderedDict_OMD = OrderedDict -else: - from typing import OrderedDict - - OrderedDict_OMD = OrderedDict[str, List[T_OMD_value]] # type: ignore[assignment, misc] +OrderedDict_OMD = OrderedDict[str, List[T_OMD_value]] # ------------------------------------------------------------- @@ -66,7 +57,7 @@ CONFIG_LEVELS: ConfigLevels_Tup = ("system", "user", "global", "repository") """The configuration level of a configuration file.""" -CONDITIONAL_INCLUDE_REGEXP = re.compile(r"(?<=includeIf )\"(gitdir|gitdir/i|onbranch|hasconfig:remote\.\*\.url):(.+)\"") +CONDITIONAL_INCLUDE_REGEXP = re.compile(r"(?<=includeif )\"(gitdir|gitdir/i|onbranch|hasconfig:remote\.\*\.url):(.+)\"") """Section pattern to detect conditional includes. See: https://git-scm.com/docs/git-config#_conditional_includes @@ -205,41 +196,67 @@ def __exit__(self, exception_type: str, exception_value: str, traceback: str) -> self._config.__exit__(exception_type, exception_value, traceback) +def _normalize_name(name: str) -> str: + """Fold section and option names, leaving quoted subsections unchanged.""" + prefix, separator, subsection = name.partition('"') + return prefix.lower() + separator + subsection + + class _OMD(OrderedDict_OMD): - """Ordered multi-dict.""" + """Ordered multi-dict matching config names while retaining their first spelling.""" + + def __init__(self, *args: Any, **kwargs: Any) -> None: + self._keymap: Dict[str, str] = {} + super().__init__(*args, **kwargs) + + def _key(self, key: str) -> str: + stored = self._keymap.get(_normalize_name(key), key) + return stored if super().__contains__(stored) else key + + def __contains__(self, key: object) -> bool: + return isinstance(key, str) and super().__contains__(self._key(key)) + + def __delitem__(self, key: str) -> None: + super().__delitem__(self._key(key)) + del self._keymap[_normalize_name(key)] def __setitem__(self, key: str, value: _T) -> None: - super().__setitem__(key, [value]) + self.setall(key, [value]) + + def clear(self) -> None: + super().clear() + self._keymap.clear() def add(self, key: str, value: Any) -> None: if key not in self: - super().__setitem__(key, [value]) + self[key] = value return - super().__getitem__(key).append(value) + self.getall(key).append(value) def setall(self, key: str, values: List[_T]) -> None: + key = self._key(key) super().__setitem__(key, values) + self._keymap[_normalize_name(key)] = key def __getitem__(self, key: str) -> Any: - return super().__getitem__(key)[-1] + return super().__getitem__(self._key(key))[-1] def getlast(self, key: str) -> Any: - return super().__getitem__(key)[-1] + return self[key] def setlast(self, key: str, value: Any) -> None: if key not in self: - super().__setitem__(key, [value]) + self[key] = value return - prior = super().__getitem__(key) - prior[-1] = value + self.getall(key)[-1] = value - def get(self, key: str, default: Union[_T, None] = None) -> Union[_T, None]: - return super().get(key, [default])[-1] + def get(self, key: str, default: Union[_T, None] = None) -> Union[_T, None]: # type: ignore[override] + return super().get(self._key(key), [default])[-1] def getall(self, key: str) -> List[_T]: - return super().__getitem__(key) + return super().__getitem__(self._key(key)) def items(self) -> List[Tuple[str, _T]]: # type: ignore[override] """List of (key, last value for key).""" @@ -288,11 +305,18 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder): other instances to write concurrently. :note: - The config is case-sensitive even when queried, hence section and option names - must match perfectly. + Section and option names are case-insensitive; quoted subsection names are + case-sensitive. Names retain their first spelling when enumerated or written. + Case variants are merged, preserving all values in the order they are read. :note: If used as a context manager, this will release the locked file. + + :note: + Options without a value are stored as ``None`` and written without ``=``. + :meth:`get_value` and :meth:`get_values` return an empty string for them, + while :meth:`getboolean` returns ``True``. An explicit empty value is + stored as an empty string and reads as ``False`` with :meth:`getboolean`. """ # { Configuration @@ -306,11 +330,15 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder): re_comment = re.compile(r"^\s*[#;]") # } END configuration - optvalueonly_source = r"\s*(?P