diff options
| -rw-r--r-- | docs/internal-fs-layout.md | 5 | ||||
| -rw-r--r-- | git_config.py | 87 | ||||
| -rw-r--r-- | git_trace2_event_log.py | 20 | ||||
| -rw-r--r-- | subcmds/sync.py | 23 | ||||
| -rw-r--r-- | tests/fixtures/test.gitconfig | 9 | ||||
| -rw-r--r-- | tests/test_git_config.py | 17 |
6 files changed, 152 insertions, 9 deletions
diff --git a/docs/internal-fs-layout.md b/docs/internal-fs-layout.md index 8cc3cabd..e3be1731 100644 --- a/docs/internal-fs-layout.md +++ b/docs/internal-fs-layout.md | |||
| @@ -146,7 +146,12 @@ Instead, you should use standard Git workflows like [git worktree] or | |||
| 146 | 146 | ||
| 147 | The `.repo/manifests.git/config` file is used to track settings for the entire | 147 | The `.repo/manifests.git/config` file is used to track settings for the entire |
| 148 | repo client checkout. | 148 | repo client checkout. |
| 149 | |||
| 149 | Most settings use the `[repo]` section to avoid conflicts with git. | 150 | Most settings use the `[repo]` section to avoid conflicts with git. |
| 151 | |||
| 152 | Everything under `[repo.syncstate.*]` is used to keep track of sync details for logging | ||
| 153 | purposes. | ||
| 154 | |||
| 150 | User controlled settings are initialized when running `repo init`. | 155 | User controlled settings are initialized when running `repo init`. |
| 151 | 156 | ||
| 152 | | Setting | `repo init` Option | Use/Meaning | | 157 | | Setting | `repo init` Option | Use/Meaning | |
diff --git a/git_config.py b/git_config.py index 3eaf201c..05d824cb 100644 --- a/git_config.py +++ b/git_config.py | |||
| @@ -13,6 +13,7 @@ | |||
| 13 | # limitations under the License. | 13 | # limitations under the License. |
| 14 | 14 | ||
| 15 | import contextlib | 15 | import contextlib |
| 16 | import datetime | ||
| 16 | import errno | 17 | import errno |
| 17 | from http.client import HTTPException | 18 | from http.client import HTTPException |
| 18 | import json | 19 | import json |
| @@ -30,6 +31,10 @@ from repo_trace import Trace | |||
| 30 | from git_command import GitCommand | 31 | from git_command import GitCommand |
| 31 | from git_refs import R_CHANGES, R_HEADS, R_TAGS | 32 | from git_refs import R_CHANGES, R_HEADS, R_TAGS |
| 32 | 33 | ||
| 34 | # Prefix that is prepended to all the keys of SyncAnalysisState's data | ||
| 35 | # that is saved in the config. | ||
| 36 | SYNC_STATE_PREFIX = 'repo.syncstate.' | ||
| 37 | |||
| 33 | ID_RE = re.compile(r'^[0-9a-f]{40}$') | 38 | ID_RE = re.compile(r'^[0-9a-f]{40}$') |
| 34 | 39 | ||
| 35 | REVIEW_CACHE = dict() | 40 | REVIEW_CACHE = dict() |
| @@ -262,6 +267,22 @@ class GitConfig(object): | |||
| 262 | self._branches[b.name] = b | 267 | self._branches[b.name] = b |
| 263 | return b | 268 | return b |
| 264 | 269 | ||
| 270 | def GetSyncAnalysisStateData(self): | ||
| 271 | """Returns data to be logged for the analysis of sync performance.""" | ||
| 272 | return {k: v for k, v in self.DumpConfigDict().items() if k.startswith(SYNC_STATE_PREFIX)} | ||
| 273 | |||
| 274 | def UpdateSyncAnalysisState(self, options, superproject_logging_data): | ||
| 275 | """Update Config's SYNC_STATE_PREFIX* data with the latest sync data. | ||
| 276 | |||
| 277 | Args: | ||
| 278 | options: Options passed to sync returned from optparse. See _Options(). | ||
| 279 | superproject_logging_data: A dictionary of superproject data that is to be logged. | ||
| 280 | |||
| 281 | Returns: | ||
| 282 | SyncAnalysisState object. | ||
| 283 | """ | ||
| 284 | return SyncAnalysisState(self, options, superproject_logging_data) | ||
| 285 | |||
| 265 | def GetSubSections(self, section): | 286 | def GetSubSections(self, section): |
| 266 | """List all subsection names matching $section.*.* | 287 | """List all subsection names matching $section.*.* |
| 267 | """ | 288 | """ |
| @@ -717,3 +738,69 @@ class Branch(object): | |||
| 717 | def _Get(self, key, all_keys=False): | 738 | def _Get(self, key, all_keys=False): |
| 718 | key = 'branch.%s.%s' % (self.name, key) | 739 | key = 'branch.%s.%s' % (self.name, key) |
| 719 | return self._config.GetString(key, all_keys=all_keys) | 740 | return self._config.GetString(key, all_keys=all_keys) |
| 741 | |||
| 742 | |||
| 743 | class SyncAnalysisState: | ||
| 744 | """Configuration options related to logging of sync state for analysis. | ||
| 745 | |||
| 746 | This object is versioned. | ||
| 747 | """ | ||
| 748 | def __init__(self, config, options, superproject_logging_data): | ||
| 749 | """Initializes SyncAnalysisState. | ||
| 750 | |||
| 751 | Saves the following data into the |config| object. | ||
| 752 | - sys.argv, options, superproject's logging data. | ||
| 753 | - repo.*, branch.* and remote.* parameters from config object. | ||
| 754 | - Current time as synctime. | ||
| 755 | - Version number of the object. | ||
| 756 | |||
| 757 | All the keys saved by this object are prepended with SYNC_STATE_PREFIX. | ||
| 758 | |||
| 759 | Args: | ||
| 760 | config: GitConfig object to store all options. | ||
| 761 | options: Options passed to sync returned from optparse. See _Options(). | ||
| 762 | superproject_logging_data: A dictionary of superproject data that is to be logged. | ||
| 763 | """ | ||
| 764 | self._config = config | ||
| 765 | now = datetime.datetime.utcnow() | ||
| 766 | self._Set('main.synctime', now.isoformat() + 'Z') | ||
| 767 | self._Set('main.version', '1') | ||
| 768 | self._Set('sys.argv', sys.argv) | ||
| 769 | for key, value in superproject_logging_data.items(): | ||
| 770 | self._Set(f'superproject.{key}', value) | ||
| 771 | for key, value in options.__dict__.items(): | ||
| 772 | self._Set(f'options.{key}', value) | ||
| 773 | config_items = config.DumpConfigDict().items() | ||
| 774 | EXTRACT_NAMESPACES = {'repo', 'branch', 'remote'} | ||
| 775 | self._SetDictionary({k: v for k, v in config_items | ||
| 776 | if not k.startswith(SYNC_STATE_PREFIX) and | ||
| 777 | k.split('.', 1)[0] in EXTRACT_NAMESPACES}) | ||
| 778 | |||
| 779 | def _SetDictionary(self, data): | ||
| 780 | """Save all key/value pairs of |data| dictionary. | ||
| 781 | |||
| 782 | Args: | ||
| 783 | data: A dictionary whose key/value are to be saved. | ||
| 784 | """ | ||
| 785 | for key, value in data.items(): | ||
| 786 | self._Set(key, value) | ||
| 787 | |||
| 788 | def _Set(self, key, value): | ||
| 789 | """Set the |value| for a |key| in the |_config| member. | ||
| 790 | |||
| 791 | |key| is prepended with the value of SYNC_STATE_PREFIX constant. | ||
| 792 | |||
| 793 | Args: | ||
| 794 | key: Name of the key. | ||
| 795 | value: |value| could be of any type. If it is 'bool', it will be saved | ||
| 796 | as a Boolean and for all other types, it will be saved as a String. | ||
| 797 | """ | ||
| 798 | if value is None: | ||
| 799 | return | ||
| 800 | sync_key = f'{SYNC_STATE_PREFIX}{key}' | ||
| 801 | if isinstance(value, str): | ||
| 802 | self._config.SetString(sync_key, value) | ||
| 803 | elif isinstance(value, bool): | ||
| 804 | self._config.SetBoolean(sync_key, value) | ||
| 805 | else: | ||
| 806 | self._config.SetString(sync_key, str(value)) | ||
diff --git a/git_trace2_event_log.py b/git_trace2_event_log.py index fae3d4c8..9c9e5a70 100644 --- a/git_trace2_event_log.py +++ b/git_trace2_event_log.py | |||
| @@ -144,6 +144,19 @@ class EventLog(object): | |||
| 144 | command_event['subcommands'] = subcommands | 144 | command_event['subcommands'] = subcommands |
| 145 | self._log.append(command_event) | 145 | self._log.append(command_event) |
| 146 | 146 | ||
| 147 | def LogConfigEvents(self, config, event_dict_name): | ||
| 148 | """Append a |event_dict_name| event for each config key in |config|. | ||
| 149 | |||
| 150 | Args: | ||
| 151 | config: Configuration dictionary. | ||
| 152 | event_dict_name: Name of the event dictionary for items to be logged under. | ||
| 153 | """ | ||
| 154 | for param, value in config.items(): | ||
| 155 | event = self._CreateEventDict(event_dict_name) | ||
| 156 | event['param'] = param | ||
| 157 | event['value'] = value | ||
| 158 | self._log.append(event) | ||
| 159 | |||
| 147 | def DefParamRepoEvents(self, config): | 160 | def DefParamRepoEvents(self, config): |
| 148 | """Append a 'def_param' event for each repo.* config key to the current log. | 161 | """Append a 'def_param' event for each repo.* config key to the current log. |
| 149 | 162 | ||
| @@ -152,12 +165,7 @@ class EventLog(object): | |||
| 152 | """ | 165 | """ |
| 153 | # Only output the repo.* config parameters. | 166 | # Only output the repo.* config parameters. |
| 154 | repo_config = {k: v for k, v in config.items() if k.startswith('repo.')} | 167 | repo_config = {k: v for k, v in config.items() if k.startswith('repo.')} |
| 155 | 168 | self.LogConfigEvents(repo_config, 'def_param') | |
| 156 | for param, value in repo_config.items(): | ||
| 157 | def_param_event = self._CreateEventDict('def_param') | ||
| 158 | def_param_event['param'] = param | ||
| 159 | def_param_event['value'] = value | ||
| 160 | self._log.append(def_param_event) | ||
| 161 | 169 | ||
| 162 | def ErrorEvent(self, msg, fmt): | 170 | def ErrorEvent(self, msg, fmt): |
| 163 | """Append a 'error' event to the current log.""" | 171 | """Append a 'error' event to the current log.""" |
diff --git a/subcmds/sync.py b/subcmds/sync.py index 74617544..ed656b8c 100644 --- a/subcmds/sync.py +++ b/subcmds/sync.py | |||
| @@ -282,7 +282,7 @@ later is required to fix a server side protocol bug. | |||
| 282 | """Returns True if current-branch or use-superproject options are enabled.""" | 282 | """Returns True if current-branch or use-superproject options are enabled.""" |
| 283 | return opt.current_branch_only or git_superproject.UseSuperproject(opt, self.manifest) | 283 | return opt.current_branch_only or git_superproject.UseSuperproject(opt, self.manifest) |
| 284 | 284 | ||
| 285 | def _UpdateProjectsRevisionId(self, opt, args, load_local_manifests): | 285 | def _UpdateProjectsRevisionId(self, opt, args, load_local_manifests, superproject_logging_data): |
| 286 | """Update revisionId of every project with the SHA from superproject. | 286 | """Update revisionId of every project with the SHA from superproject. |
| 287 | 287 | ||
| 288 | This function updates each project's revisionId with SHA from superproject. | 288 | This function updates each project's revisionId with SHA from superproject. |
| @@ -293,6 +293,7 @@ later is required to fix a server side protocol bug. | |||
| 293 | args: Arguments to pass to GetProjects. See the GetProjects | 293 | args: Arguments to pass to GetProjects. See the GetProjects |
| 294 | docstring for details. | 294 | docstring for details. |
| 295 | load_local_manifests: Whether to load local manifests. | 295 | load_local_manifests: Whether to load local manifests. |
| 296 | superproject_logging_data: A dictionary of superproject data that is to be logged. | ||
| 296 | 297 | ||
| 297 | Returns: | 298 | Returns: |
| 298 | Returns path to the overriding manifest file instead of None. | 299 | Returns path to the overriding manifest file instead of None. |
| @@ -312,6 +313,7 @@ later is required to fix a server side protocol bug. | |||
| 312 | submodules_ok=opt.fetch_submodules) | 313 | submodules_ok=opt.fetch_submodules) |
| 313 | update_result = superproject.UpdateProjectsRevisionId(all_projects) | 314 | update_result = superproject.UpdateProjectsRevisionId(all_projects) |
| 314 | manifest_path = update_result.manifest_path | 315 | manifest_path = update_result.manifest_path |
| 316 | superproject_logging_data['updatedrevisionid'] = bool(manifest_path) | ||
| 315 | if manifest_path: | 317 | if manifest_path: |
| 316 | self._ReloadManifest(manifest_path, load_local_manifests) | 318 | self._ReloadManifest(manifest_path, load_local_manifests) |
| 317 | else: | 319 | else: |
| @@ -964,8 +966,14 @@ later is required to fix a server side protocol bug. | |||
| 964 | self._UpdateManifestProject(opt, mp, manifest_name) | 966 | self._UpdateManifestProject(opt, mp, manifest_name) |
| 965 | 967 | ||
| 966 | load_local_manifests = not self.manifest.HasLocalManifests | 968 | load_local_manifests = not self.manifest.HasLocalManifests |
| 967 | if git_superproject.UseSuperproject(opt, self.manifest): | 969 | use_superproject = git_superproject.UseSuperproject(opt, self.manifest) |
| 968 | manifest_name = self._UpdateProjectsRevisionId(opt, args, load_local_manifests) or opt.manifest_name | 970 | superproject_logging_data = { |
| 971 | 'superproject': use_superproject, | ||
| 972 | 'haslocalmanifests': bool(self.manifest.HasLocalManifests), | ||
| 973 | } | ||
| 974 | if use_superproject: | ||
| 975 | manifest_name = self._UpdateProjectsRevisionId( | ||
| 976 | opt, args, load_local_manifests, superproject_logging_data) or opt.manifest_name | ||
| 969 | 977 | ||
| 970 | if self.gitc_manifest: | 978 | if self.gitc_manifest: |
| 971 | gitc_manifest_projects = self.GetProjects(args, | 979 | gitc_manifest_projects = self.GetProjects(args, |
| @@ -1079,6 +1087,15 @@ later is required to fix a server side protocol bug. | |||
| 1079 | file=sys.stderr) | 1087 | file=sys.stderr) |
| 1080 | sys.exit(1) | 1088 | sys.exit(1) |
| 1081 | 1089 | ||
| 1090 | # Log the previous sync analysis state from the config. | ||
| 1091 | self.git_event_log.LogConfigEvents(mp.config.GetSyncAnalysisStateData(), | ||
| 1092 | 'previous_sync_state') | ||
| 1093 | |||
| 1094 | # Update and log with the new sync analysis state. | ||
| 1095 | mp.config.UpdateSyncAnalysisState(opt, superproject_logging_data) | ||
| 1096 | self.git_event_log.LogConfigEvents(mp.config.GetSyncAnalysisStateData(), | ||
| 1097 | 'current_sync_state') | ||
| 1098 | |||
| 1082 | if not opt.quiet: | 1099 | if not opt.quiet: |
| 1083 | print('repo sync has finished successfully.') | 1100 | print('repo sync has finished successfully.') |
| 1084 | 1101 | ||
diff --git a/tests/fixtures/test.gitconfig b/tests/fixtures/test.gitconfig index 9b3f2574..e3f51db3 100644 --- a/tests/fixtures/test.gitconfig +++ b/tests/fixtures/test.gitconfig | |||
| @@ -11,3 +11,12 @@ | |||
| 11 | intk = 10k | 11 | intk = 10k |
| 12 | intm = 10m | 12 | intm = 10m |
| 13 | intg = 10g | 13 | intg = 10g |
| 14 | [repo "syncstate.main"] | ||
| 15 | synctime = 2021-07-29T19:18:53.201328Z | ||
| 16 | version = 1 | ||
| 17 | [repo "syncstate.sys"] | ||
| 18 | argv = ['/usr/bin/pytest-3'] | ||
| 19 | [repo "syncstate.superproject"] | ||
| 20 | test = false | ||
| 21 | [repo "syncstate.options"] | ||
| 22 | verbose = true | ||
diff --git a/tests/test_git_config.py b/tests/test_git_config.py index 3300c12f..44ff5974 100644 --- a/tests/test_git_config.py +++ b/tests/test_git_config.py | |||
| @@ -104,6 +104,23 @@ class GitConfigReadOnlyTests(unittest.TestCase): | |||
| 104 | for key, value in TESTS: | 104 | for key, value in TESTS: |
| 105 | self.assertEqual(value, self.config.GetInt('section.%s' % (key,))) | 105 | self.assertEqual(value, self.config.GetInt('section.%s' % (key,))) |
| 106 | 106 | ||
| 107 | def test_GetSyncAnalysisStateData(self): | ||
| 108 | """Test config entries with a sync state analysis data.""" | ||
| 109 | superproject_logging_data = {} | ||
| 110 | superproject_logging_data['test'] = False | ||
| 111 | options = type('options', (object,), {})() | ||
| 112 | options.verbose = 'true' | ||
| 113 | TESTS = ( | ||
| 114 | ('superproject.test', 'false'), | ||
| 115 | ('options.verbose', 'true'), | ||
| 116 | ('main.version', '1'), | ||
| 117 | ) | ||
| 118 | self.config.UpdateSyncAnalysisState(options, superproject_logging_data) | ||
| 119 | sync_data = self.config.GetSyncAnalysisStateData() | ||
| 120 | for key, value in TESTS: | ||
| 121 | self.assertEqual(sync_data[f'{git_config.SYNC_STATE_PREFIX}{key}'], value) | ||
| 122 | self.assertTrue(sync_data[f'{git_config.SYNC_STATE_PREFIX}main.synctime']) | ||
| 123 | |||
| 107 | 124 | ||
| 108 | class GitConfigReadWriteTests(unittest.TestCase): | 125 | class GitConfigReadWriteTests(unittest.TestCase): |
| 109 | """Read/write tests of the GitConfig class.""" | 126 | """Read/write tests of the GitConfig class.""" |
