sync command #12

Merged
krauterbaquette merged 25 commits from cmd-sync into main 2024-12-22 14:44:05 +00:00

Added sync command

This is the core command for sac. It is used to stage, commit and sync changes with the remote.
Per default it will execute:

  • git add .
  • git commit -m "MESSAGE"
  • git push

If the push command fails, it will try to pull changes first and afterwards push again.
A merge conflict should be handled gracefully. This means to inform the user to resolve the merge conflict inside a Editor.

After resolving the merge conflicts sac sync should be able to be run to finish the merge process and afterwards sync again.

Added sync command This is the core command for `sac`. It is used to stage, commit and sync changes with the remote. \ Per default it will execute: - `git add .` - `git commit -m "MESSAGE"` - `git push` If the push command fails, it will try to pull changes first and afterwards push again. \ A merge conflict should be handled gracefully. This means to inform the user to resolve the merge conflict inside a Editor. After resolving the merge conflicts `sac sync` should be able to be run to finish the merge process and afterwards sync again.
started sync command
Some checks failed
Rust Checks / Run Rust Check (push) Failing after 2s
2e8953dd1e
> created boilerplate

tried to create a commit trough the 'gitlib2' crate,
however this is kinda hard, because one has to customly
create a tree to commit.

might switch to a simple git add / git commit wrapper for simplicity
switched to git wrapping for commit
Some checks failed
Rust Checks / Run Rust Check (push) Failing after 3s
42c0a991b7
Commiting changes is now handled by a git wrapper as it is much more easy.
This might change in the future, if it becomes a bottleneck.
removed unused parameter
Some checks failed
Rust Checks / Run Rust Check (push) Failing after 3s
8a60a5272c
added logging for changed files
Some checks failed
Rust Checks / Run Rust Check (push) Failing after 3s
9e2ea90081
- this logs created, deleted and modified files
- renamed files is currently not supported (marked as an improvement)
refactored commit logic
Some checks failed
Rust Checks / Run Rust Check (push) Failing after 3s
c3bc0cb05b
- now inside own namespace for better seperation with sync feature
improved error handeling: nothing to commit
Some checks failed
Rust Checks / Run Rust Check (push) Failing after 3s
c304803145
started remote synchronization
Some checks failed
Rust Checks / Run Rust Check (push) Failing after 2s
fefbd73d25
improved output style
Some checks failed
Rust Checks / Run Rust Check (push) Failing after 3s
77e56f8f7b
- removed 'commited changes' log because file changed info is enough
- fixed color bug -> colors whould go on after the changed log
Author
Owner

TODO

Improve error messages

  • handle connection problems
  • handle no internet connection
  • handle unresolved merge conflicts
### TODO Improve error messages - [x] handle connection problems - [x] handle no internet connection - [ ] handle unresolved merge conflicts
pull remote changes if push fails
Some checks failed
Rust Checks / Run Rust Check (push) Failing after 3s
ac95ca4039
- print info about merge conflicts if they happen
added push retry logic
Some checks failed
Rust Checks / Run Rust Check (push) Failing after 3s
3a4fece203
refactored push error resolution -> use single function
Some checks failed
Rust Checks / Run Rust Check (push) Failing after 3s
411bb81ae6
improved error messages: no internet connection
Some checks failed
Rust Checks / Run Rust Check (push) Failing after 3s
b316e0053d
updated merge conflict message
Some checks failed
Rust Checks / Run Rust Check (push) Failing after 2s
c7ea63ff36
- highlighted merge conflict (as it is the important part)
- removed the link, because merge conflict resolution is diffrent in every editor so a 'general' link would be hard to find and really abstract. Also the user can 'just google' what it means
hacky implementation for commiting resolved merge conflicts
Some checks failed
Rust Checks / Run Rust Check (push) Failing after 2s
811d0c063d
see src/command/sync.rs:81 for more information
krauterbaquette force-pushed cmd-sync from 811d0c063d
Some checks failed
Rust Checks / Run Rust Check (push) Failing after 2s
to f411a6df6f 2024-12-21 17:40:17 +00:00
Compare
include staged files changed log
Some checks failed
Rust Checks / Run Rust Check (push) Failing after 2s
2a837a26db
refactored status display (changed files)
Some checks failed
Rust Checks / Run Rust Check (push) Failing after 2s
09e8d54f7f
this will enable to check for merge conflicts that are still ongoing
improved 'nothing to commit' error
Some checks failed
Rust Checks / Run Rust Check (push) Failing after 2s
bffa07d4ab
shows an tip to sync remote changes with -n flag
Author
Owner

TODO

implement a check before commiting code, that there are no unresolved merge conflicts (git diff --check).
There should be an option to ignore this check with a flag (--ignore-no-merge --allow-unresolved) because one might want to showcase a merge conflict layout, which would (most likely) be detected as a merge conflict itself

  • done
### TODO implement a check before commiting code, that there are no unresolved merge conflicts (`git diff --check`). There should be an option to ignore this check with a flag (~`--ignore-no-merge`~ `--allow-unresolved`) because one might want to showcase a merge conflict layout, which would (most likely) be detected as a merge conflict itself - [x] done
Author
Owner

Issue

when inside a subdirectory, the git repo will not be detected, which will lead to and error.

  • fixed
### Issue when inside a subdirectory, the git repo will not be detected, which will lead to and error. - [ ] fixed
Added conflict sync protection
Some checks failed
Rust Checks / Run Rust Check (push) Failing after 2s
6d6fd89af8
error out when a file stil has merge conflicts but the user tries to sync them
fix conflict sync protection
Some checks failed
Rust Checks / Run Rust Check (push) Failing after 3s
bc57f54277
sync protection would not detect merge conflicts if they were staged before running 'sac sync'
added flag to ignore unresolved merge conflict checks
Some checks failed
Rust Checks / Run Rust Check (push) Failing after 2s
810684e999
this is usefull only when dealing with files that showcase how merge conflicts look like
(as they would get flagged for beeing a merge conflict when they aren't)

with this feature flag they can be synced nontheless (by disableing the check)
fix: git repo not detected when inside sub directory
Some checks failed
Rust Checks / Run Rust Check (push) Failing after 3s
77da0d3a47
Author
Owner

Test

This is the main feature of sac so testing might take a bit longer...
A remote repository is needed for testing as well as two clones of said repository.

$ git clone https://git.solarpunk.social/GTA-HEG/demo.git demo
$ git clone https://git.solarpunk.social/GTA-HEG/demo.git demo2

One might to create a custom branch for testing purposes as there will be a lot of commits
This setup can also be achieved with two local clones which sync to a local bare repository

All error messages should be useful

Create commit

$ echo "hey" > new.txt
$ sac sync

(The commit message should be: sac synchronization)

Create commit (with message)

$ echo "with message" > new.txt
$ sac sync -m "this has a message
> which is even multiline"

The commit should have the given commit message.
Multiline messages should be supported

Create commit with no changes

$ sac sync

This should error

Create local commit

$ echo "only local" > local.txt
$ sac sync --local

This should create the commit only locally and not sync it with the remote

Sync only remote

Inside clone 1

$ echo "on remote" > remote.txt
$ sac sync

Inside clone 2

$ echo "not on remote" > not-remote.txt
$ sac sync --no-save

This should pull the remote.txt file into clone 2, but not-remote.txt should be unstaged (and not on the remote)

Sync remote (ahead)

Inside clone 1

$ echo "now ahead" > ahead.txt
$ sac sync -m "comes first"

Inside clone 2

$ echo "change" > change.txt
$ sac sync -m "comes second"

This should pull ahead.txt and push change.txt afterwards.
commit order:

  1. "comes first"
  2. "comes second"

Sync remote (merge conflict)

Inside clone 1

$ echo "clone 1" > file.txt
$ sac sync

Inside clone 2

$ echo "clone 2" > file.txt
$ sac sync

This should result in a merge conflict.

Commit (while merge conflict)

Inside clone 2

$ sac sync

This should be executed while the merge conflict is still present
This should error with no commit beeing made (because of the merge conflict)

Resolve conflicts

$ echo "resolved" > file.txt
$ sac sync

This should resolve the merge conflicts and sync them with the remote

No internet

For simulation disconnect from the internet

$ echo "no internet" > no-internet.txt
$ sac sync

This should fail to sync to the remote with a usefull error message

# Test This is the main feature of `sac` so testing might take a bit longer... \ A remote repository is needed for testing as well as two clones of said repository. ```bash $ git clone https://git.solarpunk.social/GTA-HEG/demo.git demo ``` ```bash $ git clone https://git.solarpunk.social/GTA-HEG/demo.git demo2 ``` > One might to create a custom branch for testing purposes as there will be a lot of commits > This setup can also be achieved with two local clones which sync to a local bare repository > All error messages should be useful ## Create commit ```bash $ echo "hey" > new.txt $ sac sync ``` (The commit message should be: `sac synchronization`) ## Create commit (with message) ```bash $ echo "with message" > new.txt $ sac sync -m "this has a message > which is even multiline" ``` The commit should have the given commit message. \ Multiline messages should be supported ## Create commit with no changes ```bash $ sac sync ``` This should error ## Create local commit ```bash $ echo "only local" > local.txt $ sac sync --local ``` This should create the commit only locally and not sync it with the remote ## Sync only remote > Inside **clone 1** ```bash $ echo "on remote" > remote.txt $ sac sync ``` > Inside **clone 2** ```bash $ echo "not on remote" > not-remote.txt $ sac sync --no-save ``` This should pull the `remote.txt` file into **clone 2**, but `not-remote.txt` should be unstaged (and not on the remote) ## Sync remote (ahead) > Inside **clone 1** ```bash $ echo "now ahead" > ahead.txt $ sac sync -m "comes first" ``` > Inside **clone 2** ```bash $ echo "change" > change.txt $ sac sync -m "comes second" ``` This should pull `ahead.txt` and push `change.txt` afterwards. \ commit order: 1. `"comes first"` 2. `"comes second"` ## Sync remote (merge conflict) > Inside **clone 1** ```bash $ echo "clone 1" > file.txt $ sac sync ``` > Inside **clone 2** ```bash $ echo "clone 2" > file.txt $ sac sync ``` This should result in a merge conflict. ### Commit (while merge conflict) > Inside **clone 2** ```bash $ sac sync ``` > This should be executed while the merge conflict is still present This should error with no commit beeing made (because of the merge conflict) ### Resolve conflicts ```bash $ echo "resolved" > file.txt $ sac sync ``` This should resolve the merge conflicts and sync them with the remote ## No internet > For simulation disconnect from the internet ```bash $ echo "no internet" > no-internet.txt $ sac sync ``` This should fail to sync to the remote with a usefull error message
krauterbaquette changed title from WIP: sync command to sync command 2024-12-21 22:58:19 +00:00
aviac approved these changes 2024-12-22 05:48:15 +00:00
Dismissed
aviac left a comment
Contributor

Ist nicht alles so relevant. Wenn du noch fragen zu irgendwas hast, schreib mir nochmal. Ich schau hier nicht so auf die notifications

Ist nicht alles so relevant. Wenn du noch fragen zu irgendwas hast, schreib mir nochmal. Ich schau hier nicht so auf die notifications
@ -0,0 +8,20 @@
/// This is the same as running:
/// - git add .
/// - git commit -m "sac synchronization"
/// - git push
/// If the remote is ahead, the push is retried with the following commands:
/// - git pull --rebase
/// - git push
#[clap(verbatim_doc_comment)]
pub struct Cmd {
#[arg(
short,
long,
value_name = "message",
default_value = "sac synchronization"
)]
/// custom message for the changes
message: String,
#[arg(short, long, default_value = "false")]
/// commit changes only to the local rempo
Contributor

Typo

Typo
krauterbaquette marked this conversation as resolved
@ -0,0 +23,20 @@
/// custom message for the changes
message: String,
#[arg(short, long, default_value = "false")]
/// commit changes only to the local rempo
local: bool,
#[arg(short, long, default_value = "false")]
/// do not create a new commit and only sync the remote repo with the local
no_save: bool,
#[arg(long, default_value = "false")]
/// this will ignore checks for unresolved merge conflicts
allow_unresolved: bool,
}
impl Cmd {
pub fn execute(&self) -> anyhow::Result<()> {
let repo = Repository::discover(".").context("not inside a git repository")?;
if !self.no_save {
Contributor

Doppelte Verneinung macht es bisschen unnötig schwer zu lesen als Menschen. Also vielleicht einfach das field save nennen und auf true defaulten

Doppelte Verneinung macht es bisschen unnötig schwer zu lesen als Menschen. Also vielleicht einfach das field save nennen und auf true defaulten
Author
Owner

ich glaube ich werde das feld einfach in --remote umbenennen. das macht dann vielleicht auch offensichtilcher, dass es das gegenstück zu "local" ist.

macht es zwar alleine betrachtet etwas ungenauer was genau die flag macht, aber da kan ich ja noch doc-types schreiben

ich glaube ich werde das feld einfach in `--remote` umbenennen. das macht dann vielleicht auch offensichtilcher, dass es das gegenstück zu "local" ist. macht es zwar alleine betrachtet etwas ungenauer was genau die flag macht, aber da kan ich ja noch doc-types schreiben
krauterbaquette marked this conversation as resolved
@ -0,0 +29,20 @@
#[arg(short, long, default_value = "false")]
/// do not create a new commit and only sync the remote repo with the local
no_save: bool,
#[arg(long, default_value = "false")]
/// this will ignore checks for unresolved merge conflicts
allow_unresolved: bool,
}
impl Cmd {
pub fn execute(&self) -> anyhow::Result<()> {
let repo = Repository::discover(".").context("not inside a git repository")?;
if !self.no_save {
commit::save_changes(&repo, &self.message, self.allow_unresolved)?;
}
if !self.local {
remote::synchronize()?;
}
Contributor

Vielleicht macht hier noch eine message sinn wenn no save und local an sind. Weil da passiert ja nix.

Vielleicht macht hier noch eine message sinn wenn no save und local an sind. Weil da passiert ja nix.
Author
Owner

good point, adde ich noch

good point, adde ich noch
krauterbaquette marked this conversation as resolved
@ -0,0 +55,20 @@
use crate::command::sync::util::git_root;
use anyhow::Context;
use git2::Repository;
use std::process::Command;
pub fn save_changes(
repo: &Repository,
commit_message: &str,
allow_unresolved: bool,
) -> anyhow::Result<()> {
let status = git_status::StatusReport::new(&repo)?;
// error out when there are merge conflicts
if !allow_unresolved {
status.assume_no_conflicts()?;
}
let cwd = git_root()?;
let add = Command::new("git")
.args(["add", "."])
Contributor

Der Punkt steht schon für "alles im aktuellem working directory". Ist das hier nötig, wenn wir eh das ganze repo adden? Wenn ja warum?

Der Punkt steht schon für "alles im aktuellem working directory". Ist das hier nötig, wenn wir eh das ganze repo adden? Wenn ja warum?
Author
Owner

Man könnte theoretisch das adden auch im commit machen, jedoch weiß ich nicht, ob es dafür auch eine möglichkeit im git rebase git, das werde ich mir nochmal anschauen.

Ansonsten muss das git add hier stehen bleiben.

Man könnte theoretisch das adden auch im commit machen, jedoch weiß ich nicht, ob es dafür auch eine möglichkeit im `git rebase` git, das werde ich mir nochmal anschauen. Ansonsten muss das git add hier stehen bleiben.
Author
Owner

Okay also ich habe jetzt nichts in der rebase config dafür gesehen. ich würde es deswegen mal drinn lassen

Okay also ich habe jetzt nichts in der rebase config dafür gesehen. ich würde es deswegen mal drinn lassen
krauterbaquette marked this conversation as resolved
@ -0,0 +83,20 @@
// This expectes, that the user does not run `sac sync` during a 'custom' rebase
// but only after a `sac sync` that resulted in merge conflicts.
let is_merge = util::is_rebase()?;
if is_merge {
// Warning: this is a dirty hack
//
// git rebase --continue will spawn an editor to provide the commit message
// the `-c` flag overwrites this editor configuration for this single command
// to be "echo 'COMMIT_MESSAGE' > "
// git will invoke this command and append the file name of the commit file.
// that way to COMMIT_MESSAGE will be written to said file.
//
// For this hack to work 'echo' and the pipe operator '>' must be valid syntax
// on the users maschine, however as they are wide-spread operators this should
// on most maschines™
let merge = Command::new("git")
.args([
"-c",
&format!("core.editor=echo '{commit_message}' > "),
"rebase",
Contributor

Hab mir is_rebase noch nicht angeschaut, aber wenn das wirklich wie der Variablennamen is_merge vermuten lässt auch merges umfasst: funktioniert das hier dann auch bei Leuten, die nicht als default strategy rebase sondern merge eingestellt haben?

Hab mir is_rebase noch nicht angeschaut, aber wenn das wirklich wie der Variablennamen is_merge vermuten lässt auch merges umfasst: funktioniert das hier dann auch bei Leuten, die nicht als default strategy rebase sondern merge eingestellt haben?
krauterbaquette marked this conversation as resolved
@ -0,0 +175,20 @@
pub struct StatusReport {
created: Vec<String>,
modified: Vec<String>,
deleted: Vec<String>,
merged: Vec<String>,
typechange: Vec<String>,
}
impl StatusReport {
pub fn new(repo: &Repository) -> anyhow::Result<StatusReport> {
let mut status = StatusOptions::new();
status.show(StatusShow::IndexAndWorkdir);
status.include_untracked(true);
status.include_unmodified(true);
let unstaged = repo
.statuses(Some(&mut status))
.context("failed to list changed files")?;
let mut report = StatusReport {
Contributor

Du kannst für StatusReport einfach #[derive(Default)] machen und dann hier StatusReport::default() nutzen. Ist ein bisschen kürzer

Du kannst für StatusReport einfach #[derive(Default)] machen und dann hier StatusReport::default() nutzen. Ist ein bisschen kürzer
krauterbaquette marked this conversation as resolved
@ -0,0 +216,20 @@
}
return Ok(report);
}
/// Check whether all merge conflicts have been resolved
/// Will error out if there are still merge conflicts
pub fn assume_no_conflicts(&self) -> anyhow::Result<()> {
let is_rebase = util::is_rebase()?;
if !is_rebase {
return Ok(());
}
let mut msg = "Unresolved merge conflicts found in:\n".to_string();
let mut had_conflicts = false;
let root = util::git_root()?;
'files: for file in self
.created
.iter()
.chain(self.modified.iter())
Contributor

Nice, progress im iter-land

Nice, progress im iter-land
krauterbaquette marked this conversation as resolved
@ -0,0 +282,20 @@
StatusType::Typechange => &self.typechange,
};
if files.len() == 0 {
return out;
}
let mut out = out + &status.header();
for path in files.iter() {
out += &status.file(path);
}
return out;
}
pub fn log(&self) {
let out = "".to_string();
let out = self.list_files(out, StatusType::Created);
let out = self.list_files(out, StatusType::Modified);
let out = self.list_files(out, StatusType::Merged);
let out = self.list_files(out, StatusType::Deleted);
let out = self.list_files(out, StatusType::Typechange);
print!("{out}");
Contributor

Debug code?

Debug code?
Author
Owner

Ne das loggen ist schon intensional.
finde es sieht eigentlich ganz hübsch aus, wenn man nochmal sieht, welche datein man gerade commited hat.

also vor allem falls man irgendwie .env files commited, dass einem das wenigstens angezeigt wird und man dann nochmal nachdenken kann, ob das gerade wirklich gewollt war...

Ne das loggen ist schon intensional. finde es sieht eigentlich ganz hübsch aus, wenn man nochmal sieht, welche datein man gerade commited hat. also vor allem falls man irgendwie .env files commited, dass einem das wenigstens angezeigt wird und man dann nochmal nachdenken kann, ob das gerade wirklich gewollt war...
Contributor

Ahh sorry, hatte das auch ein bisschen falsch gelesen. Hatte nicht gesehen, dass du quasi das out immer weiter updatest. Dachte die variable shadowed einfach immer die vorherige

Ahh sorry, hatte das auch ein bisschen falsch gelesen. Hatte nicht gesehen, dass du quasi das out immer weiter updatest. Dachte die variable shadowed einfach immer die vorherige
krauterbaquette marked this conversation as resolved
@ -0,0 +310,20 @@
use spinners::{Spinner, Spinners};
use std::process::Command;
pub fn synchronize() -> anyhow::Result<()> {
let mut sp = Spinner::new(Spinners::Line, "sync changes with remote".into());
match push_changes() {
Err(err) => {
if let Err(err) = match_push_error(&err.to_string(), true) {
sp.stop_and_persist("", "failed to sync with remote".into());
anyhow::bail!(err);
}
sp.stop_and_persist(">", "remote is ahead".into());
}
Ok(_) => {
sp.stop_and_persist("🗸", "synced changes with remote".into());
return Ok(());
}
};
// failed to push -> pull and push again
Contributor

Besteht practice allgemein in git ist eigentlich immer erst zu pullen. Vielleicht lohnt es sich den ersten push Versuch zu lassen und einfach immer mit pull zu starten?

Besteht practice allgemein in git ist eigentlich immer erst zu pullen. Vielleicht lohnt es sich den ersten push Versuch zu lassen und einfach immer mit pull zu starten?
krauterbaquette marked this conversation as resolved
@ -0,0 +348,20 @@
return Ok(());
}
fn push_changes() -> anyhow::Result<()> {
let push = Command::new("git")
.args(["push"])
.output()
.context("failed to execute git")?;
if !push.status.success() {
anyhow::bail!(
String::from_utf8(push.stderr).unwrap_or("failed to run 'git push'".into()),
)
}
return Ok(());
}
fn pull_changes() -> anyhow::Result<()> {
let pull = Command::new("git")
.args(["pull", "--rebase"])
Contributor

Ahh OK, du rebased immer. Solange die user nur SAC nehmen sollte es also keine normalen merges geben. Das erledigt dann einen dee vorherigen Kommentare

Ahh OK, du rebased immer. Solange die user nur SAC nehmen sollte es also keine normalen merges geben. Das erledigt dann einen dee vorherigen Kommentare
Author
Owner

Ja, sac hat generell probleme wenn es mit git zusammen genutzt wird (z.B. kann es sync nur nutzen wenn eine upstream branch definiert ist, was bei sac branch passiert, bei git branch aber nicht unbedingt).

Vielleicht sollte man da nochmal irgendwo in der documentation ein verweis machen, dass es alleine genutzt werden sollte (oder halt nur wenn man weiß was man tut

Ja, `sac` hat generell probleme wenn es mit `git` zusammen genutzt wird (z.B. kann es sync nur nutzen wenn eine upstream branch definiert ist, was bei `sac branch` passiert, bei `git branch` aber nicht unbedingt). Vielleicht sollte man da nochmal irgendwo in der documentation ein verweis machen, dass es alleine genutzt werden sollte (oder halt nur wenn man _weiß was man tut_
krauterbaquette marked this conversation as resolved
fixed typo
Some checks failed
Rust Checks / Run Rust Check (push) Failing after 3s
16b3659aa7
error message when using local & nosave flag at the same time
Some checks failed
Rust Checks / Run Rust Check (push) Failing after 3s
93ff1e6216
shortend StatusReport creation with ::default()
Some checks failed
Rust Checks / Run Rust Check (push) Failing after 2s
9e03ae2728
- Adressing: GTA-HEG/SAC#12 (comment)
renamed --no-save to --remote
Some checks failed
Rust Checks / Run Rust Check (push) Failing after 2s
b276fbfc36
Adressing: GTA-HEG/SAC#12 (comment)

this was made to improve variable naming
krauterbaquette force-pushed cmd-sync from b276fbfc36
Some checks failed
Rust Checks / Run Rust Check (push) Failing after 2s
to fdecc05eb3
Some checks failed
Rust Checks / Run Rust Check (push) Failing after 2s
2024-12-22 14:16:05 +00:00
Compare
aviac approved these changes 2024-12-22 14:41:15 +00:00
krauterbaquette referenced this pull request from a commit 2024-12-22 14:44:07 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
krauterbaquette/sac!12
No description provided.