Line data Source code
1 : /*! 2 : * \file esys/repo/exe/cmdsync.cpp 3 : * \brief 4 : * 5 : * \cond 6 : * __legal_b__ 7 : * 8 : * Copyright (c) 2020-2022 Michel Gillet 9 : * Distributed under the MIT License. 10 : * (See accompanying file LICENSE.txt or 11 : * copy at https://opensource.org/licenses/MIT) 12 : * 13 : * __legal_e__ 14 : * \endcond 15 : * 16 : */ 17 : 18 : #include "esys/repo/esysrepo_prec.h" 19 : #include "esys/repo/exe/cmdsync.h" 20 : #include "esys/repo/exe/nested.h" 21 : #include "esys/repo/manifest/capture.h" 22 : #include "esys/repo/manifest/sync.h" 23 : #include "esys/repo/manifest/syncrepos.h" 24 : #include "esys/repo/resultcode.h" 25 : #include "esys/repo/tui/is_tty.h" 26 : #include "esys/repo/tui/run_compact_sync_tui.h" 27 : 28 : #include <esys/trace/call.h> 29 : #include <esys/trace/macros.h> 30 : 31 : #include <boost/filesystem.hpp> 32 : 33 : #include <cstdlib> 34 : #include <cstring> 35 : #include <iostream> 36 : #include <sstream> 37 : 38 : namespace esys::repo::exe 39 : { 40 : 41 : namespace 42 : { 43 : 44 26 : bool env_flag_enabled(const char *name) 45 : { 46 26 : const char *value = std::getenv(name); 47 26 : if (value == nullptr || value[0] == '\0') return false; 48 0 : if (std::strcmp(value, "1") == 0) return true; 49 0 : if (std::strcmp(value, "true") == 0) return true; 50 0 : if (std::strcmp(value, "TRUE") == 0) return true; 51 0 : if (std::strcmp(value, "yes") == 0) return true; 52 0 : if (std::strcmp(value, "YES") == 0) return true; 53 0 : if (std::strcmp(value, "on") == 0) return true; 54 0 : if (std::strcmp(value, "ON") == 0) return true; 55 : return false; 56 : } 57 : 58 : } // namespace 59 : 60 39 : CmdSync::CmdSync() 61 78 : : Cmd("Sync") 62 : { 63 39 : } 64 : 65 39 : CmdSync::~CmdSync() = default; 66 : 67 1 : void CmdSync::set_force(bool force) 68 : { 69 1 : m_force = force; 70 1 : } 71 : 72 27 : bool CmdSync::get_force() const 73 : { 74 27 : return m_force; 75 : } 76 : 77 28 : void CmdSync::set_branch(const std::string &branch) 78 : { 79 28 : m_branch = branch; 80 28 : } 81 : 82 93 : const std::string &CmdSync::get_branch() const 83 : { 84 93 : return m_branch; 85 : } 86 : 87 2 : void CmdSync::set_rev_set(const std::string &rev_set) 88 : { 89 2 : m_rev_set = rev_set; 90 2 : } 91 : 92 54 : const std::string &CmdSync::get_rev_set() const 93 : { 94 54 : return m_rev_set; 95 : } 96 : 97 26 : Result CmdSync::apply_revision_set_selection() 98 : { 99 26 : Result result; 100 26 : ETRC_CALL_RET_NP(result); 101 : 102 52 : if (get_manifest() == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::MANIFEST_IS_NULLPTR)); 103 : 104 26 : std::string preferred = get_rev_set(); 105 104 : if (preferred.empty() && get_config_folder() != nullptr && get_config_folder()->get_config() != nullptr) 106 104 : preferred = get_config_folder()->get_config()->get_revision_set(); 107 : 108 26 : result = get_manifest()->select_revision_set(preferred); 109 26 : if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result)); 110 : 111 26 : if (!get_rev_set().empty() && get_config_folder() != nullptr) 112 : { 113 0 : auto cfg = get_config_folder()->get_or_new_config(); 114 0 : cfg->set_revision_set(get_rev_set()); 115 0 : result = get_config_folder()->write_config_file(); 116 0 : if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result)); 117 0 : } 118 : 119 52 : return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK)); 120 26 : } 121 : 122 26 : Result CmdSync::apply_prefer_branch() 123 : { 124 26 : Result result; 125 26 : ETRC_CALL_RET_NP(result); 126 : 127 52 : if (get_config_folder() == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK)); 128 : 129 26 : if (get_branch().empty()) 130 : { 131 66 : if (get_config_folder()->get_config() != nullptr) 132 66 : set_branch(get_config_folder()->get_config()->get_prefer_branch()); 133 44 : return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK)); 134 : } 135 : 136 4 : auto cfg = get_config_folder()->get_or_new_config(); 137 4 : cfg->set_prefer_branch(get_branch()); 138 4 : result = get_config_folder()->write_config_file(); 139 4 : if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result)); 140 8 : return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK)); 141 26 : } 142 : 143 26 : Result CmdSync::apply_revision_pins() 144 : { 145 26 : Result result; 146 26 : ETRC_CALL_RET_NP(result); 147 : 148 52 : if (get_manifest() == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::MANIFEST_IS_NULLPTR)); 149 52 : if (get_config_folder() == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::CONFIG_FOLDER_IS_NULLPTR)); 150 : 151 : // open() already loaded pins; refresh in case they changed mid-session 152 26 : result = get_config_folder()->load_pins_file(); 153 26 : if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result)); 154 : 155 78 : get_manifest()->set_revision_pins(get_config_folder()->get_or_new_revision_pins()); 156 78 : return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK)); 157 26 : } 158 : 159 1 : void CmdSync::set_alt_address(bool alt_address) 160 : { 161 1 : m_alt_address = alt_address; 162 1 : } 163 1 : bool CmdSync::get_alt_address() const 164 : { 165 1 : return m_alt_address; 166 : } 167 : 168 0 : void CmdSync::set_multi(bool multi) 169 : { 170 0 : m_multi = multi; 171 0 : } 172 : 173 26 : bool CmdSync::get_multi() const 174 : { 175 26 : return m_multi; 176 : } 177 : 178 0 : void CmdSync::set_tui(bool tui) 179 : { 180 0 : m_tui = tui; 181 0 : } 182 : 183 26 : bool CmdSync::get_tui() const 184 : { 185 26 : return m_tui; 186 : } 187 : 188 0 : void CmdSync::set_tui_demo(bool tui_demo) 189 : { 190 0 : m_tui_demo = tui_demo; 191 0 : if (tui_demo) m_tui = true; 192 0 : } 193 : 194 0 : bool CmdSync::get_tui_demo() const 195 : { 196 0 : return m_tui_demo; 197 : } 198 : 199 3 : void CmdSync::set_recurse_submodules(manifest::RecurseSubmodules recurse) 200 : { 201 3 : m_recurse_submodules = recurse; 202 3 : } 203 : 204 29 : manifest::RecurseSubmodules CmdSync::get_recurse_submodules() const 205 : { 206 29 : return m_recurse_submodules; 207 : } 208 : 209 26 : Result CmdSync::sync_manifest() 210 : { 211 26 : Result result; 212 26 : ETRC_CALL_RET_NP(result); 213 : 214 26 : manifest::Sync sync; 215 : 216 52 : if (get_config_folder() == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::CMD_CONFIG_FOLDER_NULLPTR)); 217 : 218 26 : auto config = get_config_folder()->get_config(); 219 26 : if (config != nullptr && !config->get_manifest_local().empty()) 220 : { 221 1 : info("Local manifest: skip git update of the manifest."); 222 2 : return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK)); 223 : } 224 : 225 25 : sync.set_config_folder(get_config_folder()); 226 25 : sync.set_git(get_git()); 227 25 : sync.set_logger_if(get_logger_if()); 228 25 : if (!get_branch().empty()) sync.set_branch(get_branch()); 229 : 230 50 : return ETRC_RET(ESYSREPO_RESULT(sync.run())); 231 26 : } 232 : 233 0 : Result CmdSync::handle_capture_item(std::shared_ptr<manifest::Capture::Item> item) 234 : { 235 0 : Result result; 236 0 : ETRC_CALL_RET(result, item); 237 : 238 0 : if (get_workspace_path().empty()) return ETRC_RET(ESYSREPO_RESULT(ResultCode::CMD_WORKSPACE_PATH_IS_EMPTY)); 239 : 240 0 : boost::filesystem::path git_path_folder = get_workspace_path(); 241 0 : git_path_folder /= item->get_path(); 242 0 : git_path_folder = git_path_folder.lexically_normal().make_preferred(); 243 : 244 0 : auto git_helper = new_git_helper(); 245 0 : result = git_helper->open(git_path_folder.string(), log::Level::DEBUG); 246 0 : if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result)); 247 : 248 0 : GitHelper::AutoClose auto_close(git_helper.get()); 249 : 250 0 : bool dirty = false; 251 0 : result = git_helper->is_dirty(dirty, log::Level::DEBUG); 252 0 : if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result)); 253 : 254 : // If there is a remote defined, create it and fetch it 255 0 : if (!item->get_remote_name().empty()) 256 : { 257 0 : result = git_helper->get_git()->add_remote(item->get_remote_name(), item->get_remote_url()); 258 0 : if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result)); 259 : 260 0 : result = git_helper->get_git()->fetch(item->get_remote_name()); 261 0 : if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result)); 262 : } 263 : 264 : // Checkout the new revision 265 0 : result = git_helper->checkout(item->get_revision(), false, log::Level::DEBUG); 266 0 : if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result)); 267 : 268 0 : return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK)); 269 0 : } 270 : 271 0 : Result CmdSync::do_multi() 272 : { 273 0 : Result result; 274 0 : ETRC_CALL_RET_NP(result); 275 : 276 : // First we need to get the working folder 277 0 : std::string folder_path; 278 : 279 0 : if (!get_folder().empty()) 280 0 : folder_path = get_folder(); 281 0 : else if (!get_workspace_path().empty()) 282 0 : folder_path = get_workspace_path(); 283 : else 284 0 : folder_path = boost::filesystem::current_path().string(); 285 0 : if (folder_path.empty()) return ETRC_RET(ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR)); 286 : 287 0 : auto git_folder_path = find_git_root_path(folder_path); 288 0 : auto git_helper = new_git_helper(); 289 : 290 0 : set_git_folder_path(git_folder_path); 291 0 : result = git_helper->open(git_folder_path, log::Level::DEBUG); 292 0 : if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result)); 293 : 294 0 : GitHelper::AutoClose auto_close(git_helper.get()); 295 : 296 0 : int result_int = git_helper->get_git()->add_notes_ref("esysrepo"); 297 0 : if (result_int < 0) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)); 298 : 299 0 : result = git_helper->get_git()->fetch_all_notes(); 300 0 : if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result)); 301 : 302 0 : git::Commit last_commit; 303 : 304 0 : result = git_helper->get_git()->get_last_commit(last_commit, true); 305 0 : if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result)); 306 : 307 0 : std::vector<std::shared_ptr<git::Note>> notes; 308 0 : result_int = last_commit.get_notes("esysrepo", notes); 309 0 : if (result_int < 0) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)); 310 : 311 0 : std::ostringstream oss; 312 : 313 0 : oss << "Found " << notes.size() << " esysrepo multi note."; 314 : 315 0 : info(oss.str()); 316 : 317 0 : if (notes.empty()) return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK)); 318 0 : if (notes.size() != 1) return ETRC_RET(ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR)); 319 : 320 0 : std::string note_text = notes[0]->get_message(); 321 0 : auto capture = std::make_shared<manifest::Capture>(); 322 0 : result = load_capture(capture, note_text); 323 0 : if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result)); 324 : 325 0 : oss.str(""); 326 0 : print_capture(oss, capture); 327 0 : info(oss.str()); 328 : 329 0 : Result rresult; 330 0 : for (auto repo : capture->get_items()) 331 : { 332 0 : result = handle_capture_item(repo); 333 0 : if (result.error()) 334 : { 335 0 : std::ostringstream e_oss; 336 : 337 0 : e_oss << "repo '" << repo->get_path() << "' could set revivions '" << repo->get_revision() << "'"; 338 0 : error(e_oss.str()); 339 0 : rresult.add(result); 340 0 : } 341 0 : } 342 : 343 0 : return ETRC_RET(ESYSREPO_RESULT(rresult)); 344 0 : } 345 : 346 0 : void CmdSync::set_git_folder_path(const std::string &git_folder_path) 347 : { 348 0 : m_git_folder_path = git_folder_path; 349 0 : } 350 : 351 0 : const std::string &CmdSync::get_git_folder_path() const 352 : { 353 0 : return m_git_folder_path; 354 : } 355 : 356 26 : Result CmdSync::apply_sync_folder_filters(manifest::SyncRepos &sync_repos) 357 : { 358 26 : if (get_sub_args().size() != 0) 359 4 : sync_repos.set_folders_to_sync(get_sub_args()); 360 22 : else if (get_groups().size() != 0) 361 : { 362 2 : std::vector<std::string> repos; 363 : 364 4 : for (auto &group : get_groups()) 365 : { 366 2 : auto group_ptr = get_manifest()->get_groups().find_group_by_name(group); 367 2 : if (group_ptr == nullptr) continue; 368 14 : for (auto repo : group_ptr->get_repos()) repos.push_back(repo->get_path()); 369 2 : } 370 : 371 2 : sync_repos.set_folders_to_sync(repos); 372 2 : } 373 : 374 26 : if (get_sub_args().empty()) return ESYSREPO_RESULT(ResultCode::OK); 375 8 : if (get_manifest() == nullptr) return ESYSREPO_RESULT(ResultCode::MANIFEST_IS_NULLPTR); 376 : 377 4 : bool any = false; 378 8 : for (auto location : get_manifest()->get_locations()) 379 : { 380 9 : for (auto repo : location->get_repos()) 381 : { 382 27 : if (sync_repos.is_repo_to_be_synced(repo)) 383 : { 384 4 : any = true; 385 4 : break; 386 : } 387 5 : } 388 4 : if (any) break; 389 0 : } 390 4 : if (any) return ESYSREPO_RESULT(ResultCode::OK); 391 : 392 0 : std::ostringstream oss; 393 0 : oss << "No repository matches"; 394 0 : for (const auto &arg : get_sub_args()) oss << " '" << arg << "'"; 395 0 : error(oss.str()); 396 0 : return ESYSREPO_RESULT(ResultCode::MANIFEST_REPO_UNKNOWN, oss.str()); 397 0 : } 398 : 399 26 : Result CmdSync::impl_run() 400 : { 401 26 : Result result; 402 26 : ETRC_CALL_RET_NP(result); 403 : 404 26 : if (env_flag_enabled("ESYSREPO_TUI_DEMO")) set_tui_demo(true); 405 : 406 26 : if (get_tui()) return ETRC_RET(ESYSREPO_RESULT(impl_run_tui())); 407 : 408 26 : if (get_force()) warn("Option --force-sync is not implemented yet"); 409 : 410 26 : result = default_handling_folder_workspace(); 411 26 : if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result)); 412 : 413 26 : result = open_esysrepo_folder(); 414 26 : if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result)); 415 : 416 26 : result = apply_prefer_branch(); 417 26 : if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result)); 418 : 419 26 : result = sync_manifest(); 420 26 : if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result)); 421 : 422 26 : result = load_manifest(); 423 26 : if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result)); 424 : 425 26 : result = apply_revision_set_selection(); 426 26 : if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result)); 427 : 428 26 : result = apply_revision_pins(); 429 26 : if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result)); 430 : 431 26 : manifest::SyncRepos sync_repos; 432 : 433 26 : if (!get_branch().empty()) sync_repos.set_branch(get_branch()); 434 : 435 26 : result = apply_sync_folder_filters(sync_repos); 436 26 : if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result)); 437 : 438 26 : if (get_debug()) sync_repos.set_log_level(log::Level::DEBUG); 439 : 440 26 : sync_repos.set_job_count(get_job_count()); 441 26 : sync_repos.set_logger_if(get_logger_if()); 442 26 : sync_repos.set_config_folder(get_config_folder()); 443 26 : sync_repos.set_git(get_git()); 444 26 : sync_repos.set_manifest(get_manifest()); 445 26 : sync_repos.set_tui(false); 446 26 : sync_repos.set_recurse_submodules(get_recurse_submodules()); 447 : 448 26 : get_git()->detect_ssh_agent(true); 449 : 450 26 : result = sync_repos.run(); 451 26 : if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result)); 452 : 453 26 : NestedContext nested_scratch; 454 26 : NestedContext *nested_ctx = get_nested_context() != nullptr ? get_nested_context() : &nested_scratch; 455 26 : set_nested_context(nested_ctx); 456 52 : result = for_each_nested(*this, NestedMissingMode::Fail, *nested_ctx, 457 0 : [this](const std::string &child_abs, std::shared_ptr<manifest::Repository>) { 458 0 : CmdSync child; 459 0 : prepare_nested_cmd(*this, child, child_abs); 460 0 : child.set_force(get_force()); 461 0 : child.set_branch(get_branch()); 462 0 : child.set_rev_set(get_rev_set()); 463 0 : child.set_alt_address(get_alt_address()); 464 0 : child.set_multi(false); 465 0 : child.set_recurse_submodules(get_recurse_submodules()); 466 0 : return child.run(); 467 26 : }); 468 26 : if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result)); 469 : 470 52 : if (!get_multi()) return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK)); 471 0 : return ETRC_RET(ESYSREPO_RESULT(do_multi())); 472 26 : } 473 : 474 0 : Result CmdSync::impl_run_tui() 475 : { 476 0 : Result result; 477 0 : ETRC_CALL_RET_NP(result); 478 : 479 : #ifndef ESYSREPO_HAVE_TUI 480 : error("--tui requested but esysrepo was built without FTXUI (Conan with_tui / ESYSREPO_WITH_TUI)"); 481 : return ETRC_RET(ESYSREPO_RESULT(ResultCode::NOT_IMPLEMENTED)); 482 : #else 483 0 : if (!tui::is_tty()) 484 : { 485 0 : const char *msg = 486 : "--tui requires a real Win32 console (cmd.exe, PowerShell, or Windows " 487 : "Terminal). Git Bash/mintty and redirected stdout are not supported."; 488 0 : error(msg); 489 0 : std::cerr << "ERROR: " << msg << std::endl; 490 0 : return ETRC_RET(ESYSREPO_RESULT(ResultCode::GENERIC_ERROR)); 491 : } 492 : 493 0 : if (get_force()) warn("Option --force-sync is not implemented yet"); 494 : 495 0 : const std::size_t ui_workers = 496 0 : get_job_count() <= 0 ? std::size_t{1} : static_cast<std::size_t>(get_job_count()); 497 : 498 0 : auto work = [this](progress::ProgressSession &session, tui::SyncTuiControl &control) -> Result { 499 0 : auto prev_logger = get_logger_if(); 500 0 : if (control.logger) set_logger_if(control.logger); 501 : 502 0 : auto restore_logger = [&]() { 503 0 : if (prev_logger) set_logger_if(prev_logger); 504 0 : }; 505 : 506 0 : if (control.set_phase) control.set_phase("Loading workspace…"); 507 : 508 0 : Result phase = default_handling_folder_workspace(); 509 0 : if (phase.error()) 510 : { 511 0 : restore_logger(); 512 : return phase; 513 : } 514 : 515 0 : phase = open_esysrepo_folder(); 516 0 : if (phase.error()) 517 : { 518 0 : restore_logger(); 519 : return phase; 520 : } 521 : 522 0 : phase = apply_prefer_branch(); 523 0 : if (phase.error()) 524 : { 525 0 : restore_logger(); 526 : return phase; 527 : } 528 : 529 0 : if (control.set_phase) control.set_phase("Loading manifest…"); 530 : 531 0 : phase = sync_manifest(); 532 0 : if (phase.error()) 533 : { 534 0 : restore_logger(); 535 : return phase; 536 : } 537 : 538 0 : phase = load_manifest(); 539 0 : if (phase.error()) 540 : { 541 0 : restore_logger(); 542 : return phase; 543 : } 544 : 545 0 : phase = apply_revision_set_selection(); 546 0 : if (phase.error()) 547 : { 548 0 : restore_logger(); 549 : return phase; 550 : } 551 : 552 0 : phase = apply_revision_pins(); 553 0 : if (phase.error()) 554 : { 555 0 : restore_logger(); 556 : return phase; 557 : } 558 : 559 0 : manifest::SyncRepos sync_repos; 560 : 561 0 : if (!get_branch().empty()) sync_repos.set_branch(get_branch()); 562 : 563 0 : phase = apply_sync_folder_filters(sync_repos); 564 0 : if (phase.error()) 565 : { 566 0 : restore_logger(); 567 : return phase; 568 : } 569 : 570 0 : if (get_debug()) sync_repos.set_log_level(log::Level::DEBUG); 571 : 572 0 : sync_repos.set_job_count(get_job_count()); 573 0 : sync_repos.set_logger_if(get_logger_if()); 574 0 : sync_repos.set_config_folder(get_config_folder()); 575 0 : sync_repos.set_git(get_git()); 576 0 : sync_repos.set_manifest(get_manifest()); 577 0 : sync_repos.set_tui(false); 578 0 : sync_repos.set_alt_address(get_alt_address()); 579 0 : sync_repos.set_recurse_submodules(get_recurse_submodules()); 580 : 581 0 : get_git()->detect_ssh_agent(true); 582 0 : if (control.logger) get_git()->set_logger_if(control.logger); 583 : 584 0 : phase = sync_repos.run_with_session(session, &control); 585 0 : restore_logger(); 586 : return phase; 587 0 : }; 588 : 589 0 : tui::SyncTuiOptions tui_options; 590 0 : tui_options.demo = get_tui_demo(); 591 0 : result = tui::run_compact_sync_tui(work, {}, ui_workers, get_logger_if(), tui_options); 592 0 : if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result)); 593 : 594 0 : if (!get_multi()) return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK)); 595 0 : return ETRC_RET(ESYSREPO_RESULT(do_multi())); 596 : #endif 597 0 : } 598 : 599 : } // namespace esys::repo::exe