Implement git commit and push using credentials stored in credential manager
This commit is contained in:
parent
467d86f53d
commit
aa9209b7d6
91
Cargo.lock
generated
91
Cargo.lock
generated
|
|
@ -64,6 +64,21 @@ version = "0.2.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04"
|
||||
|
||||
[[package]]
|
||||
name = "android-tzdata"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
|
||||
|
||||
[[package]]
|
||||
name = "android_system_properties"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arrayref"
|
||||
version = "0.3.9"
|
||||
|
|
@ -153,6 +168,12 @@ version = "1.21.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.9.0"
|
||||
|
|
@ -249,6 +270,20 @@ version = "0.2.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.39"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"wasm-bindgen",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "combine"
|
||||
version = "4.6.7"
|
||||
|
|
@ -416,7 +451,7 @@ version = "0.5.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412"
|
||||
dependencies = [
|
||||
"libloading 0.7.4",
|
||||
"libloading 0.8.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -856,6 +891,29 @@ version = "0.4.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc"
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.61"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
"iana-time-zone-haiku",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
"windows-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone-haiku"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_collections"
|
||||
version = "1.5.0"
|
||||
|
|
@ -1077,6 +1135,17 @@ dependencies = [
|
|||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "keyring"
|
||||
version = "3.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f8fe839464d4e4b37d756d7e910063696af79a7e877282cb1825e4ec5f10833"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"log",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kqueue"
|
||||
version = "1.0.8"
|
||||
|
|
@ -1356,6 +1425,15 @@ version = "2.0.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e0826a989adedc2a244799e823aece04662b66609d96af8dff7ac6df9a8925d"
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_enum"
|
||||
version = "0.7.3"
|
||||
|
|
@ -1584,7 +1662,9 @@ dependencies = [
|
|||
name = "obsidian-git-backup"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"git2",
|
||||
"keyring",
|
||||
"notify",
|
||||
"tray-icon",
|
||||
"winit",
|
||||
|
|
@ -2498,6 +2578,15 @@ version = "0.4.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.45.0"
|
||||
|
|
|
|||
|
|
@ -8,3 +8,5 @@ winit = "0.30.8"
|
|||
notify = "8.0.0"
|
||||
tray-icon = "0.19.2"
|
||||
git2 = "0.20.0"
|
||||
chrono = "0.4.39"
|
||||
keyring = { version = "3.6.1", features = ["windows-native"] }
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ This script automates the process of backing up your Obsidian vault to a Git rep
|
|||
|
||||
- [x] Trigger backup after file changes with delay
|
||||
- [x] Maintain git repo in a seperate folder to not have the repo synced by syncthing (copy changed files over)
|
||||
- [ ] Push changes to remote repository
|
||||
- [x] Push changes to remote repository
|
||||
- [ ] Some error management
|
||||
- [x] Tray Menu
|
||||
- [x] Exit
|
||||
|
|
|
|||
|
|
@ -1,8 +1,87 @@
|
|||
pub fn backup_changes() {
|
||||
use chrono::Local;
|
||||
use git2::{Cred, IndexAddOption, PushOptions, RemoteCallbacks, Repository, StatusOptions};
|
||||
use keyring::Entry;
|
||||
use std::path::Path;
|
||||
|
||||
pub fn backup_changes(repo_path: &Path) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let start = std::time::Instant::now();
|
||||
|
||||
println!("Checking for changes...");
|
||||
// TODO log changed files first
|
||||
// Open the repository at the provided path
|
||||
let repo = Repository::open(repo_path)?;
|
||||
|
||||
// Gather the repository statuses, including untracked files.
|
||||
let mut status_opts = StatusOptions::new();
|
||||
status_opts.include_untracked(true);
|
||||
let statuses = repo.statuses(Some(&mut status_opts))?;
|
||||
|
||||
// If no changes are detected, exit early
|
||||
if statuses.is_empty() {
|
||||
println!("No changes detected");
|
||||
println!("Backup took {:?}", start.elapsed());
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Print out all changes.
|
||||
println!("Detected changes:");
|
||||
for entry in statuses.iter() {
|
||||
let path = entry.path().unwrap_or("<unknown>");
|
||||
let status = entry.status();
|
||||
println!(" - {}: {:?}", path, status);
|
||||
}
|
||||
|
||||
// Add all changes to the index
|
||||
let mut index = repo.index()?;
|
||||
index.add_all(["*"].iter(), IndexAddOption::DEFAULT, None)?;
|
||||
index.write()?;
|
||||
|
||||
// Write the index to a tree
|
||||
let tree_id = index.write_tree()?;
|
||||
let tree = repo.find_tree(tree_id)?;
|
||||
|
||||
// Use the repository signature for both author and committer
|
||||
let signature = repo.signature()?;
|
||||
|
||||
// Determine the parent commit
|
||||
let parent_commit = repo.head()?.peel_to_commit()?;
|
||||
|
||||
// Generate commit message with current timestamp
|
||||
let commit_message = format!("{} Autobackup", Local::now().format("%Y-%m-%d %H:%M:%S"));
|
||||
|
||||
// Create commit
|
||||
repo.commit(
|
||||
Some("HEAD"),
|
||||
&signature,
|
||||
&signature,
|
||||
&commit_message,
|
||||
&tree,
|
||||
&[&parent_commit],
|
||||
)?;
|
||||
|
||||
// Set up remote push with credentials callback
|
||||
let mut callbacks = RemoteCallbacks::new();
|
||||
callbacks.credentials(move |_url, username, _allowed_types| {
|
||||
let user = username.unwrap_or("git");
|
||||
let token = get_git_token();
|
||||
Cred::userpass_plaintext(user, &token)
|
||||
});
|
||||
|
||||
// Push changes
|
||||
let mut push_options = PushOptions::new();
|
||||
push_options.remote_callbacks(callbacks);
|
||||
let mut remote = repo.find_remote("origin")?;
|
||||
remote.push(&["refs/heads/main"], Some(&mut push_options))?;
|
||||
|
||||
println!("Changes committed and pushed successfully.");
|
||||
println!("Backup took {:?}", start.elapsed());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_git_token() -> String {
|
||||
// Get token from Windows Credential Manager
|
||||
let service = "obsidian-git-backup";
|
||||
let username = "git";
|
||||
let entry = Entry::new(service, username).unwrap();
|
||||
entry
|
||||
.get_password()
|
||||
.expect("Git access token could not be retrieved from Windows Credential Manager")
|
||||
}
|
||||
|
|
|
|||
13
src/main.rs
13
src/main.rs
|
|
@ -1,5 +1,4 @@
|
|||
use notify::{Event, RecursiveMode, Watcher};
|
||||
|
||||
use std::path::Path;
|
||||
use std::time::{Duration, Instant};
|
||||
use tray_icon::{
|
||||
|
|
@ -16,8 +15,8 @@ use winit::{
|
|||
mod filesync;
|
||||
mod gitpush;
|
||||
|
||||
const SOURCE_VAULT: &str = "D:\\test\\obsidian-vault-source"; // syncthing vault folder
|
||||
const REPO_VAULT: &str = "D:\\test\\obsidian-vault-test"; // git repo folder
|
||||
const SOURCE_VAULT: &str = "X:\\test\\obsidian-vault-source"; // syncthing vault folder
|
||||
const REPO_VAULT: &str = "X:\\test\\obsidian-vault-test"; // git repo folder
|
||||
|
||||
#[derive(Debug)]
|
||||
enum UserEvent {
|
||||
|
|
@ -70,7 +69,8 @@ impl ApplicationHandler<UserEvent> for App {
|
|||
println!("---> Backup triggered by menu");
|
||||
// Handle push now menu item
|
||||
self.last_change_time = None;
|
||||
gitpush::backup_changes();
|
||||
let repo_dir = Path::new(REPO_VAULT);
|
||||
gitpush::backup_changes(repo_dir).expect("Changes could not be pushed");
|
||||
}
|
||||
id if id == self.menu_exit_id.as_ref().unwrap() => {
|
||||
println!("---> Exit triggert by menu");
|
||||
|
|
@ -101,7 +101,8 @@ impl ApplicationHandler<UserEvent> for App {
|
|||
if now.duration_since(last_change_time) > self.duration_threshold {
|
||||
println!("---> Backup triggered by file change");
|
||||
self.last_change_time = None;
|
||||
gitpush::backup_changes();
|
||||
let repo_dir = Path::new(REPO_VAULT);
|
||||
gitpush::backup_changes(repo_dir).expect("Changes could not be pushed");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -126,7 +127,7 @@ fn main() {
|
|||
filesync::sync_directory(source_dir, repo_dir, Some(".git"))
|
||||
.expect("Directories could not be synced");
|
||||
println!("Sync took {:?}", start.elapsed());
|
||||
gitpush::backup_changes();
|
||||
gitpush::backup_changes(repo_dir).expect("Changes could not be pushed");
|
||||
|
||||
// Create event loop
|
||||
let event_loop = EventLoop::<UserEvent>::with_user_event().build().unwrap();
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user