extract-tracker 0.2.3__tar.gz → 0.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.
- {extract_tracker-0.2.3 → extract_tracker-0.3.0}/PKG-INFO +1 -1
- {extract_tracker-0.2.3 → extract_tracker-0.3.0}/pyproject.toml +1 -1
- {extract_tracker-0.2.3 → extract_tracker-0.3.0}/python/src/extract_tracker.egg-info/PKG-INFO +1 -1
- {extract_tracker-0.2.3 → extract_tracker-0.3.0}/rust/Cargo.lock +1 -1
- {extract_tracker-0.2.3 → extract_tracker-0.3.0}/rust/Cargo.toml +1 -1
- {extract_tracker-0.2.3 → extract_tracker-0.3.0}/rust/src/app.rs +2 -0
- {extract_tracker-0.2.3 → extract_tracker-0.3.0}/rust/src/db.rs +25 -0
- {extract_tracker-0.2.3 → extract_tracker-0.3.0}/rust/src/ui/layout.rs +8 -1
- {extract_tracker-0.2.3 → extract_tracker-0.3.0}/rust/src/ui/popup.rs +121 -13
- {extract_tracker-0.2.3 → extract_tracker-0.3.0}/rust/src/ui/summary.rs +9 -1
- {extract_tracker-0.2.3 → extract_tracker-0.3.0}/MANIFEST.in +0 -0
- {extract_tracker-0.2.3 → extract_tracker-0.3.0}/README.md +0 -0
- {extract_tracker-0.2.3 → extract_tracker-0.3.0}/manual/config.md +0 -0
- {extract_tracker-0.2.3 → extract_tracker-0.3.0}/manual/mcp.md +0 -0
- {extract_tracker-0.2.3 → extract_tracker-0.3.0}/manual/packaging.md +0 -0
- {extract_tracker-0.2.3 → extract_tracker-0.3.0}/manual/sync.md +0 -0
- {extract_tracker-0.2.3 → extract_tracker-0.3.0}/manual/tui.md +0 -0
- {extract_tracker-0.2.3 → extract_tracker-0.3.0}/manual/usage.md +0 -0
- {extract_tracker-0.2.3 → extract_tracker-0.3.0}/python/src/extract/__init__.py +0 -0
- {extract_tracker-0.2.3 → extract_tracker-0.3.0}/python/src/extract/__main__.py +0 -0
- {extract_tracker-0.2.3 → extract_tracker-0.3.0}/python/src/extract/cli_query.py +0 -0
- {extract_tracker-0.2.3 → extract_tracker-0.3.0}/python/src/extract/experiment.py +0 -0
- {extract_tracker-0.2.3 → extract_tracker-0.3.0}/python/src/extract/init.py +0 -0
- {extract_tracker-0.2.3 → extract_tracker-0.3.0}/python/src/extract/mcp.py +0 -0
- {extract_tracker-0.2.3 → extract_tracker-0.3.0}/python/src/extract/metrics.py +0 -0
- {extract_tracker-0.2.3 → extract_tracker-0.3.0}/python/src/extract/release_versioning.py +0 -0
- {extract_tracker-0.2.3 → extract_tracker-0.3.0}/python/src/extract/run.py +0 -0
- {extract_tracker-0.2.3 → extract_tracker-0.3.0}/python/src/extract/store.py +0 -0
- {extract_tracker-0.2.3 → extract_tracker-0.3.0}/python/src/extract/sync.py +0 -0
- {extract_tracker-0.2.3 → extract_tracker-0.3.0}/python/src/extract_tracker.egg-info/SOURCES.txt +0 -0
- {extract_tracker-0.2.3 → extract_tracker-0.3.0}/python/src/extract_tracker.egg-info/dependency_links.txt +0 -0
- {extract_tracker-0.2.3 → extract_tracker-0.3.0}/python/src/extract_tracker.egg-info/entry_points.txt +0 -0
- {extract_tracker-0.2.3 → extract_tracker-0.3.0}/python/src/extract_tracker.egg-info/requires.txt +0 -0
- {extract_tracker-0.2.3 → extract_tracker-0.3.0}/python/src/extract_tracker.egg-info/top_level.txt +0 -0
- {extract_tracker-0.2.3 → extract_tracker-0.3.0}/rust/src/artifact.rs +0 -0
- {extract_tracker-0.2.3 → extract_tracker-0.3.0}/rust/src/config.rs +0 -0
- {extract_tracker-0.2.3 → extract_tracker-0.3.0}/rust/src/event.rs +0 -0
- {extract_tracker-0.2.3 → extract_tracker-0.3.0}/rust/src/keys.rs +0 -0
- {extract_tracker-0.2.3 → extract_tracker-0.3.0}/rust/src/main.rs +0 -0
- {extract_tracker-0.2.3 → extract_tracker-0.3.0}/rust/src/model.rs +0 -0
- {extract_tracker-0.2.3 → extract_tracker-0.3.0}/rust/src/ui/chart.rs +0 -0
- {extract_tracker-0.2.3 → extract_tracker-0.3.0}/rust/src/ui/compare.rs +0 -0
- {extract_tracker-0.2.3 → extract_tracker-0.3.0}/rust/src/ui/dashboard.rs +0 -0
- {extract_tracker-0.2.3 → extract_tracker-0.3.0}/rust/src/ui/detail.rs +0 -0
- {extract_tracker-0.2.3 → extract_tracker-0.3.0}/rust/src/ui/diff.rs +0 -0
- {extract_tracker-0.2.3 → extract_tracker-0.3.0}/rust/src/ui/heatmap.rs +0 -0
- {extract_tracker-0.2.3 → extract_tracker-0.3.0}/rust/src/ui/help.rs +0 -0
- {extract_tracker-0.2.3 → extract_tracker-0.3.0}/rust/src/ui/lineage.rs +0 -0
- {extract_tracker-0.2.3 → extract_tracker-0.3.0}/rust/src/ui/mod.rs +0 -0
- {extract_tracker-0.2.3 → extract_tracker-0.3.0}/rust/src/ui/registry.rs +0 -0
- {extract_tracker-0.2.3 → extract_tracker-0.3.0}/rust/src/ui/search.rs +0 -0
- {extract_tracker-0.2.3 → extract_tracker-0.3.0}/rust/src/ui/selection.rs +0 -0
- {extract_tracker-0.2.3 → extract_tracker-0.3.0}/rust/src/ui/statusbar.rs +0 -0
- {extract_tracker-0.2.3 → extract_tracker-0.3.0}/rust/src/ui/theme.rs +0 -0
- {extract_tracker-0.2.3 → extract_tracker-0.3.0}/rust/src/ui/todo.rs +0 -0
- {extract_tracker-0.2.3 → extract_tracker-0.3.0}/rust/src/ui/tree.rs +0 -0
- {extract_tracker-0.2.3 → extract_tracker-0.3.0}/setup.cfg +0 -0
- {extract_tracker-0.2.3 → extract_tracker-0.3.0}/setup.py +0 -0
|
@@ -306,6 +306,7 @@ pub struct RunBrowserState {
|
|
|
306
306
|
pub filtered: Vec<usize>,
|
|
307
307
|
pub cursor: usize,
|
|
308
308
|
pub search_query: Option<String>,
|
|
309
|
+
pub rename_buffer: Option<String>,
|
|
309
310
|
pub scroll_offset: usize,
|
|
310
311
|
}
|
|
311
312
|
|
|
@@ -318,6 +319,7 @@ impl RunBrowserState {
|
|
|
318
319
|
filtered,
|
|
319
320
|
cursor: 0,
|
|
320
321
|
search_query: None,
|
|
322
|
+
rename_buffer: None,
|
|
321
323
|
scroll_offset: 0,
|
|
322
324
|
}
|
|
323
325
|
}
|
|
@@ -854,6 +854,19 @@ impl Db {
|
|
|
854
854
|
Ok(())
|
|
855
855
|
}
|
|
856
856
|
|
|
857
|
+
/// Rename a run. Empty string clears the name. Opens a writable connection.
|
|
858
|
+
pub fn rename_run(db_path: &Path, id: &str, name: &str) -> Result<()> {
|
|
859
|
+
let conn = Connection::open(db_path)?;
|
|
860
|
+
conn.execute_batch("PRAGMA journal_mode=WAL; PRAGMA foreign_keys=ON;")?;
|
|
861
|
+
let trimmed = name.trim();
|
|
862
|
+
let value: Option<&str> = if trimmed.is_empty() { None } else { Some(trimmed) };
|
|
863
|
+
conn.execute(
|
|
864
|
+
"UPDATE runs SET name = ? WHERE id = ?",
|
|
865
|
+
params![value, id],
|
|
866
|
+
)?;
|
|
867
|
+
Ok(())
|
|
868
|
+
}
|
|
869
|
+
|
|
857
870
|
/// Set status on a single run or experiment. Opens a writable connection.
|
|
858
871
|
pub fn set_status(db_path: &Path, table: &str, id: &str, status: &str) -> Result<()> {
|
|
859
872
|
assert!(table == "runs" || table == "experiments");
|
|
@@ -1314,6 +1327,18 @@ mod tests {
|
|
|
1314
1327
|
assert_eq!(run.status, "failed");
|
|
1315
1328
|
}
|
|
1316
1329
|
|
|
1330
|
+
#[test]
|
|
1331
|
+
fn test_rename_run() {
|
|
1332
|
+
let tdb = test_db_with_path();
|
|
1333
|
+
Db::rename_run(&tdb.path, "r1", "renamed").unwrap();
|
|
1334
|
+
let run = tdb.db.get_run("r1").unwrap().unwrap();
|
|
1335
|
+
assert_eq!(run.name.as_deref(), Some("renamed"));
|
|
1336
|
+
|
|
1337
|
+
Db::rename_run(&tdb.path, "r1", " ").unwrap();
|
|
1338
|
+
let run = tdb.db.get_run("r1").unwrap().unwrap();
|
|
1339
|
+
assert!(run.name.is_none());
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1317
1342
|
#[test]
|
|
1318
1343
|
fn test_archive_experiment() {
|
|
1319
1344
|
let tdb = test_db_with_path();
|
|
@@ -82,7 +82,14 @@ impl AppLayout {
|
|
|
82
82
|
// focused panel's input handler see every keystroke unmodified.
|
|
83
83
|
let in_text_input = state.tag_picker.is_some()
|
|
84
84
|
|| state.note_input.is_some()
|
|
85
|
-
|| state.todo_input.is_some()
|
|
85
|
+
|| state.todo_input.is_some()
|
|
86
|
+
|| state
|
|
87
|
+
.run_picker
|
|
88
|
+
.as_ref()
|
|
89
|
+
.is_some_and(|picker| picker.search_query.is_some())
|
|
90
|
+
|| state.run_browser.as_ref().is_some_and(|browser| {
|
|
91
|
+
browser.search_query.is_some() || browser.rename_buffer.is_some()
|
|
92
|
+
});
|
|
86
93
|
|
|
87
94
|
if !in_text_input {
|
|
88
95
|
// Global keys: gg/G, ?, work in all views
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
use
|
|
1
|
+
use chrono::{DateTime, Local};
|
|
2
|
+
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
|
2
3
|
use ratatui::layout::Rect;
|
|
3
4
|
use ratatui::style::{Modifier, Style};
|
|
4
5
|
use ratatui::symbols::border;
|
|
@@ -216,6 +217,70 @@ impl PopupRenderer {
|
|
|
216
217
|
};
|
|
217
218
|
|
|
218
219
|
let is_searching = browser.search_query.is_some();
|
|
220
|
+
let is_renaming = browser.rename_buffer.is_some();
|
|
221
|
+
|
|
222
|
+
// Rename mode
|
|
223
|
+
if is_renaming {
|
|
224
|
+
match key.code {
|
|
225
|
+
KeyCode::Esc => {
|
|
226
|
+
browser.rename_buffer = None;
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
KeyCode::Enter => {
|
|
230
|
+
if let Some(&run_idx) = browser.filtered.get(browser.cursor) {
|
|
231
|
+
if let Some(run) = browser.runs.get(run_idx) {
|
|
232
|
+
let run_id = run.id.clone();
|
|
233
|
+
let new_name = browser.rename_buffer.take().unwrap_or_default();
|
|
234
|
+
let db_path = state.store_root.join("extract.db");
|
|
235
|
+
match crate::db::Db::rename_run(&db_path, &run_id, &new_name) {
|
|
236
|
+
Ok(()) => {
|
|
237
|
+
let trimmed = new_name.trim();
|
|
238
|
+
let value = if trimmed.is_empty() {
|
|
239
|
+
None
|
|
240
|
+
} else {
|
|
241
|
+
Some(trimmed.to_string())
|
|
242
|
+
};
|
|
243
|
+
if let Some(run) = browser.runs.get_mut(run_idx) {
|
|
244
|
+
run.name = value.clone();
|
|
245
|
+
}
|
|
246
|
+
if let Some(state_idx) = state.runs.iter().position(|r| r.id == run_id) {
|
|
247
|
+
state.runs[state_idx].name = value;
|
|
248
|
+
}
|
|
249
|
+
let _ = state.refresh_selection_summary();
|
|
250
|
+
state.notify(crate::app::NotifyLevel::Success, "Run renamed");
|
|
251
|
+
}
|
|
252
|
+
Err(err) => {
|
|
253
|
+
state.notify(
|
|
254
|
+
crate::app::NotifyLevel::Error,
|
|
255
|
+
format!("Rename failed: {err}"),
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
} else {
|
|
260
|
+
browser.rename_buffer = None;
|
|
261
|
+
}
|
|
262
|
+
} else {
|
|
263
|
+
browser.rename_buffer = None;
|
|
264
|
+
}
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
KeyCode::Backspace => {
|
|
268
|
+
if let Some(ref mut name) = browser.rename_buffer {
|
|
269
|
+
name.pop();
|
|
270
|
+
}
|
|
271
|
+
return false;
|
|
272
|
+
}
|
|
273
|
+
KeyCode::Char(c) => {
|
|
274
|
+
if accepts_text_modifiers(key) {
|
|
275
|
+
if let Some(ref mut name) = browser.rename_buffer {
|
|
276
|
+
name.push(c);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
281
|
+
_ => return false,
|
|
282
|
+
}
|
|
283
|
+
}
|
|
219
284
|
|
|
220
285
|
// Search mode
|
|
221
286
|
if is_searching {
|
|
@@ -290,6 +355,15 @@ impl PopupRenderer {
|
|
|
290
355
|
return false;
|
|
291
356
|
}
|
|
292
357
|
|
|
358
|
+
if is_rename_key(key) {
|
|
359
|
+
if let Some(&run_idx) = browser.filtered.get(browser.cursor) {
|
|
360
|
+
if let Some(run) = browser.runs.get(run_idx) {
|
|
361
|
+
browser.rename_buffer = Some(run.name.clone().unwrap_or_default());
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
return false;
|
|
365
|
+
}
|
|
366
|
+
|
|
293
367
|
if keys::matches(key, keys::SELECT) {
|
|
294
368
|
if let Some(&filtered_idx) = browser.filtered.get(browser.cursor) {
|
|
295
369
|
if let Some(run) = browser.runs.get(filtered_idx) {
|
|
@@ -499,6 +573,7 @@ impl PopupRenderer {
|
|
|
499
573
|
browser: &mut RunBrowserState,
|
|
500
574
|
) {
|
|
501
575
|
let is_searching = browser.search_query.is_some();
|
|
576
|
+
let is_renaming = browser.rename_buffer.is_some();
|
|
502
577
|
let width = POPUP_WIDTH.min(area.width.saturating_sub(4));
|
|
503
578
|
let height = POPUP_HEIGHT.min(area.height.saturating_sub(4));
|
|
504
579
|
let popup_area = centered_rect(width, height, area);
|
|
@@ -508,12 +583,16 @@ impl PopupRenderer {
|
|
|
508
583
|
let title = format!(" {} — runs ", browser.experiment_name);
|
|
509
584
|
let footer_spans = if is_searching {
|
|
510
585
|
search_footer_spans(&self.theme)
|
|
586
|
+
} else if is_renaming {
|
|
587
|
+
rename_footer_spans(&self.theme)
|
|
511
588
|
} else {
|
|
512
589
|
vec![
|
|
513
590
|
Span::styled("j/k", Style::default().fg(self.theme.accent)),
|
|
514
591
|
Span::styled(" nav ", Style::default().fg(self.theme.accent_dim)),
|
|
515
592
|
Span::styled("Enter", Style::default().fg(self.theme.accent)),
|
|
516
593
|
Span::styled(" select ", Style::default().fg(self.theme.accent_dim)),
|
|
594
|
+
Span::styled("R", Style::default().fg(self.theme.accent)),
|
|
595
|
+
Span::styled(" rename ", Style::default().fg(self.theme.accent_dim)),
|
|
517
596
|
Span::styled("/", Style::default().fg(self.theme.accent)),
|
|
518
597
|
Span::styled(" search ", Style::default().fg(self.theme.accent_dim)),
|
|
519
598
|
Span::styled("x", Style::default().fg(self.theme.accent)),
|
|
@@ -570,11 +649,19 @@ impl PopupRenderer {
|
|
|
570
649
|
Style::default()
|
|
571
650
|
};
|
|
572
651
|
|
|
573
|
-
let label =
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
652
|
+
let label = if is_cursor {
|
|
653
|
+
browser
|
|
654
|
+
.rename_buffer
|
|
655
|
+
.as_ref()
|
|
656
|
+
.map(|name| format!("{name}_ "))
|
|
657
|
+
.or_else(|| run.name.as_deref().map(|n| format!("{} ", n)))
|
|
658
|
+
.unwrap_or_default()
|
|
659
|
+
} else {
|
|
660
|
+
run.name
|
|
661
|
+
.as_deref()
|
|
662
|
+
.map(|n| format!("{} ", n))
|
|
663
|
+
.unwrap_or_default()
|
|
664
|
+
};
|
|
578
665
|
let date = format_date(run);
|
|
579
666
|
let config_summary = differing_config_summary(run, &diff_keys);
|
|
580
667
|
|
|
@@ -624,14 +711,24 @@ fn compute_scroll(cursor: usize, current_offset: usize, list_height: usize) -> u
|
|
|
624
711
|
}
|
|
625
712
|
}
|
|
626
713
|
|
|
627
|
-
/// Format a run
|
|
714
|
+
/// Format a run timestamp for display in the user's local timezone.
|
|
628
715
|
fn format_date(run: &Run) -> String {
|
|
629
|
-
run.ended_at
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
716
|
+
let raw = run.ended_at.as_deref().unwrap_or(&run.started_at);
|
|
717
|
+
format_local_timestamp(raw).unwrap_or_else(|| raw.chars().take(19).collect())
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
fn format_local_timestamp(raw: &str) -> Option<String> {
|
|
721
|
+
DateTime::parse_from_rfc3339(raw)
|
|
722
|
+
.ok()
|
|
723
|
+
.map(|dt| dt.with_timezone(&Local).format("%Y-%m-%d %H:%M:%S").to_string())
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
fn is_rename_key(key: &KeyEvent) -> bool {
|
|
727
|
+
matches!(key.code, KeyCode::Char('R')) && accepts_text_modifiers(key)
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
fn accepts_text_modifiers(key: &KeyEvent) -> bool {
|
|
731
|
+
key.modifiers == KeyModifiers::NONE || key.modifiers == KeyModifiers::SHIFT
|
|
635
732
|
}
|
|
636
733
|
|
|
637
734
|
/// Build search input line with blinking cursor.
|
|
@@ -655,6 +752,17 @@ fn search_footer_spans(theme: &Theme) -> Vec<Span<'static>> {
|
|
|
655
752
|
]
|
|
656
753
|
}
|
|
657
754
|
|
|
755
|
+
fn rename_footer_spans(theme: &Theme) -> Vec<Span<'static>> {
|
|
756
|
+
vec![
|
|
757
|
+
Span::styled("Type", Style::default().fg(theme.accent)),
|
|
758
|
+
Span::styled(" name ", Style::default().fg(theme.accent_dim)),
|
|
759
|
+
Span::styled("Enter", Style::default().fg(theme.accent)),
|
|
760
|
+
Span::styled(" save ", Style::default().fg(theme.accent_dim)),
|
|
761
|
+
Span::styled("Esc", Style::default().fg(theme.accent)),
|
|
762
|
+
Span::styled(" cancel", Style::default().fg(theme.accent_dim)),
|
|
763
|
+
]
|
|
764
|
+
}
|
|
765
|
+
|
|
658
766
|
/// Compare configs across all runs and return only the keys whose values differ.
|
|
659
767
|
fn differing_config_keys(runs: &[Run]) -> Vec<String> {
|
|
660
768
|
use std::collections::HashMap;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
use chrono::{DateTime, Local};
|
|
1
2
|
use ratatui::buffer::Buffer;
|
|
2
3
|
use ratatui::layout::Rect;
|
|
3
4
|
use ratatui::style::{Color, Modifier, Style};
|
|
@@ -219,7 +220,7 @@ impl SummaryRenderer {
|
|
|
219
220
|
}
|
|
220
221
|
|
|
221
222
|
let run = &data.runs[i];
|
|
222
|
-
let date =
|
|
223
|
+
let date = format_run_time(run);
|
|
223
224
|
let label = run.name.clone().unwrap_or_else(|| {
|
|
224
225
|
let id = &run.id;
|
|
225
226
|
if id.len() > 8 { id[id.len() - 8..].to_string() } else { id.clone() }
|
|
@@ -632,6 +633,13 @@ impl SummaryRenderer {
|
|
|
632
633
|
}
|
|
633
634
|
}
|
|
634
635
|
|
|
636
|
+
fn format_run_time(run: &Run) -> String {
|
|
637
|
+
DateTime::parse_from_rfc3339(&run.started_at)
|
|
638
|
+
.ok()
|
|
639
|
+
.map(|dt| dt.with_timezone(&Local).format("%Y-%m-%d %H:%M:%S").to_string())
|
|
640
|
+
.unwrap_or_else(|| run.started_at.chars().take(19).collect())
|
|
641
|
+
}
|
|
642
|
+
|
|
635
643
|
/// Catmull-Rom spline interpolation.
|
|
636
644
|
/// Generates `num_points` evenly spaced points along the spline that passes
|
|
637
645
|
/// through all input points. Requires at least 3 input points.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{extract_tracker-0.2.3 → extract_tracker-0.3.0}/python/src/extract_tracker.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{extract_tracker-0.2.3 → extract_tracker-0.3.0}/python/src/extract_tracker.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{extract_tracker-0.2.3 → extract_tracker-0.3.0}/python/src/extract_tracker.egg-info/requires.txt
RENAMED
|
File without changes
|
{extract_tracker-0.2.3 → extract_tracker-0.3.0}/python/src/extract_tracker.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|