diff --git a/Cargo.lock b/Cargo.lock
index 49fe6d1..ce410cf 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2,9 +2,9 @@
# It is not intended for manual editing.
[[package]]
name = "aho-corasick"
-version = "0.7.13"
+version = "0.7.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "043164d8ba5c4c3035fec9bbee8647c0261d788f3474306f93bb65901cae0e86"
+checksum = "b476ce7103678b0c6d3d395dbbae31d48ff910bd28be979ba5d48c6351131d0d"
dependencies = [
"memchr",
]
@@ -20,9 +20,9 @@ dependencies = [
[[package]]
name = "anyhow"
-version = "1.0.32"
+version = "1.0.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6b602bfe940d21c130f3895acd65221e8a61270debe89d628b9cb4e3ccb8569b"
+checksum = "a1fd36ffbb1fb7c834eac128ea8d0e310c5aeb635548f9d58861e1308d46e71c"
[[package]]
name = "atty"
@@ -37,9 +37,9 @@ dependencies = [
[[package]]
name = "autocfg"
-version = "1.0.0"
+version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
+checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "bitflags"
@@ -47,15 +47,6 @@ version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
-[[package]]
-name = "cc"
-version = "1.0.58"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f9a06fb2e53271d7c279ec1efea6ab691c35a2ae67ec0d91d7acec0caf13b518"
-dependencies = [
- "jobserver",
-]
-
[[package]]
name = "cfg-if"
version = "0.1.10"
@@ -77,6 +68,10 @@ dependencies = [
"vec_map",
]
+[[package]]
+name = "die"
+version = "1.0.0"
+
[[package]]
name = "git-url-parse"
version = "0.3.0"
@@ -92,19 +87,10 @@ dependencies = [
]
[[package]]
-name = "git2"
-version = "0.13.8"
+name = "hashbrown"
+version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e6ac22e49b7d886b6802c66662b12609452248b1bc9e87d6d83ecea3db96f557"
-dependencies = [
- "bitflags",
- "libc",
- "libgit2-sys",
- "log",
- "openssl-probe",
- "openssl-sys",
- "url",
-]
+checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
[[package]]
name = "heck"
@@ -117,9 +103,9 @@ dependencies = [
[[package]]
name = "hermit-abi"
-version = "0.1.15"
+version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3deed196b6e7f9e44a2ae8d94225d80302d81208b1bb673fd21fe634645c85a9"
+checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8"
dependencies = [
"libc",
]
@@ -136,12 +122,13 @@ dependencies = [
]
[[package]]
-name = "jobserver"
-version = "0.1.21"
+name = "indexmap"
+version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c71313ebb9439f74b00d9d2dcec36440beaf57a6aa0623068441dd7cd81a7f2"
+checksum = "55e2e4c765aa53a0424761bf9f41aa7a6ac1efa87238f59560640e27fca028f2"
dependencies = [
- "libc",
+ "autocfg",
+ "hashbrown",
]
[[package]]
@@ -152,49 +139,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
-version = "0.2.74"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a2f02823cf78b754822df5f7f268fb59822e7296276d3e069d8e8cb26a14bd10"
-
-[[package]]
-name = "libgit2-sys"
-version = "0.12.9+1.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b33bf3d9d4c45b48ae1ea7c334be69994624dc0a69f833d5d9f7605f24b552b"
-dependencies = [
- "cc",
- "libc",
- "libssh2-sys",
- "libz-sys",
- "openssl-sys",
- "pkg-config",
-]
-
-[[package]]
-name = "libssh2-sys"
-version = "0.2.18"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eafa907407504b0e683786d4aba47acf250f114d37357d56608333fd167dd0fc"
-dependencies = [
- "cc",
- "libc",
- "libz-sys",
- "openssl-sys",
- "pkg-config",
- "vcpkg",
-]
-
-[[package]]
-name = "libz-sys"
-version = "1.0.27"
+version = "0.2.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6ca8894883d250240341478bf987467332fbdd5da5c42426c69a8f93dbc302f2"
-dependencies = [
- "cc",
- "libc",
- "pkg-config",
- "vcpkg",
-]
+checksum = "2448f6066e80e3bfc792e9c98bf705b4b0fc6e8ef5b43e5889aff0eaa9c58743"
[[package]]
name = "log"
@@ -222,27 +169,9 @@ name = "mgt"
version = "2.6.0"
dependencies = [
"clap",
+ "die",
"git-url-parse",
- "git2",
-]
-
-[[package]]
-name = "openssl-probe"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de"
-
-[[package]]
-name = "openssl-sys"
-version = "0.9.58"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a842db4709b604f0fe5d1170ae3565899be2ad3d9cbc72dedc789ac0511f78de"
-dependencies = [
- "autocfg",
- "cc",
- "libc",
- "pkg-config",
- "vcpkg",
+ "toml",
]
[[package]]
@@ -251,12 +180,6 @@ version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
-[[package]]
-name = "pkg-config"
-version = "0.3.18"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d36492546b6af1463394d46f0c834346f31548646f6ba10849802c9c9a27ac33"
-
[[package]]
name = "proc-macro2"
version = "1.0.24"
@@ -289,9 +212,15 @@ dependencies = [
[[package]]
name = "regex-syntax"
-version = "0.6.18"
+version = "0.6.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8cab7a364d15cde1e505267766a2d3c4e22a843e1a601f0fa7564c0f82ced11c"
+
+[[package]]
+name = "serde"
+version = "1.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8"
+checksum = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a"
[[package]]
name = "strsim"
@@ -319,9 +248,9 @@ dependencies = [
[[package]]
name = "syn"
-version = "1.0.42"
+version = "1.0.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c51d92969d209b54a98397e1b91c8ae82d8c87a7bb87df0b29aa2ad81454228"
+checksum = "cc371affeffc477f42a221a1e4297aedcea33d47d19b61455588bd9d8f6b19ac"
dependencies = [
"proc-macro2",
"quote",
@@ -348,9 +277,19 @@ dependencies = [
[[package]]
name = "tinyvec"
-version = "0.3.3"
+version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "53953d2d3a5ad81d9f844a32f14ebb121f50b650cd59d0ee2a07cf13c617efed"
+checksum = "238ce071d267c5710f9d31451efec16c5ee22de34df17cc05e56cbc92e967117"
+
+[[package]]
+name = "toml"
+version = "0.5.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75cf45bb0bef80604d001caaec0d09da99611b3c0fd39d3080468875cdb65645"
+dependencies = [
+ "indexmap",
+ "serde",
+]
[[package]]
name = "unicode-bidi"
@@ -399,12 +338,6 @@ dependencies = [
"percent-encoding",
]
-[[package]]
-name = "vcpkg"
-version = "0.2.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6454029bf181f092ad1b853286f23e2c507d8e8194d01d92da4a55c274a5508c"
-
[[package]]
name = "vec_map"
version = "0.8.2"
diff --git a/Cargo.toml b/Cargo.toml
index 1d881a8..6801912 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -9,9 +9,10 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
-git2 = "0.13"
clap = "2.33"
+toml = { version = "0.5.7", features = ["preserve_order"] }
git-url-parse = "0.3.0"
+die = { path="die" }
[features]
gittests = []
diff --git a/README.md b/README.md
index b0b7023..675c1a2 100644
--- a/README.md
+++ b/README.md
@@ -27,22 +27,100 @@ Some features that `mgt` provides are:
## Example
-The following is one of the simplest uses of `mgt`. For more interesting use cases, [see the examples here](./examples/README.md)
+
+(click to expand example)
-The simplest usage of `mgt` is to take an entire remote repository and `split-in-as` into a subdirectory of your local repository. If you are in a local git repository, you can run:
+The following is a simple example of `mgt` usage. For more information on how to use `mgt`, [see the documentation](./doc/README.md)
+
+We first write a `repo_file` called `meta.rf`:
+
+```toml
+[repo]
+remote = "https://github.com/nikita-skobov/monorepo-git-tools"
+
+[include_as]
+"lib/mgt/src/" = "src/"
+"lib/mgt/Cargo.toml" = "Cargo.toml"
+```
+
+Next we will run `mgt split-in` to take the remote repository
+defined in the above file, fetch it, and rewrite the paths to match our rules according to the `[include_as]` section:
```sh
-mgt split-in-as https://github.com/nikita-skobov/monorepo-git-tools --as lib/mgt/ --rebase
+mgt split-in meta.rf --rebase --num-commits 1
+# output:
+Pulling from https://github.com/nikita-skobov/monorepo-git-tools
+Running filter commands on temporary branch: monorepo-git-tools
+Rebasing
+Success!
```
-This will:
-1. create a new, temporary branch (named `monorepo-git-tools`)
-2. fetch the remote repository (`https://github.com/nikita-skobov/monorepo-git-tools`) into that branch
-3. rewrite the history of this new branch such that the entire contents exist within `lib/mgt/`
-4. the `--rebase` flag tells `mgt` to then rebase that new branch onto whatever branch you started from
+We also passed 2 arguments: `--rebase` will automatically rebase the temporary created branch onto our current branch for us, and `--num-commits 1` will only fetch 1 commit from the latest HEAD of the remote repository.
+
+After running the above, we will be in a branch called `monorepo-git-tools` that was created for us, and then rebased such that it can now be fast forwared into master. Let's now merge into master, and then delete the temporary branch:
+
+```sh
+git checkout master
+git merge --ff-only monorepo-git-tools
+```
+
+Now, let's make a commit on the Cargo.toml file that we remapped to `lib/mgt/Cargo.toml`:
+
+```sh
+echo "# add a comment to the end of the file" >> lib/mgt/Cargo.toml
+git add lib/mgt/Cargo.toml
+git commit -m "contributions can be bidirectional!"
+```
+
+Now we can check if there are any contributions that we can TAKE from the remote repo according to the mapping defined in our repo file:
+
+```sh
+mgt check meta.rf
+# output:
+---
+Checking meta.rf
+Current: https://github.com/nikita-skobov/monorepo-git-tools
+Upstream: HEAD
+You are up to date. Latest commit in current exists in upstream
+```
+
+Now let's check if we have any contributions to GIVE to the remote repo:
+
+```sh
+mgt check meta.rf --local
+# output:
+---
+Checking meta.rf
+Current: HEAD
+Upstream: https://github.com/nikita-skobov/monorepo-git-tools
+upstream can take 1 commit(s) from current:
+ce9a912 contributions can be bidirectional!
+
+To perform this update you can run:
+mgt split-out meta.rf --topbase
+```
+
+We can then run the suggested command that `mgt check` outputs to contribute our latest commit back to the remote repository:
+
+```sh
+mgt split-out meta.rf --topbase
+```
+
+The `--topbase` flag will help calculate which contributions can be applied to the tip of the remote. It also creates a temporary branch that can be used to push to the remote repo. Let's do that with:
+
+```sh
+git push https://github.com/nikita-skobov/monorepo-git-tools HEAD:newbranch
+```
+
+Which will push our current HEAD to a new remote branch called `newbranch`. Once our changes are up there, we can go back to master and delete our current temporary branch.
+
+```sh
+git branch -D monorepo-git-tools
+```
-After those steps, you will be on the `monorepo-git-tools` branch, and you can merge it however you want back into your starting branch.
+That's the end of the example :)
+
## How does it work?
@@ -55,7 +133,7 @@ go in a `repo_file` are:
- excluding files/folders
- specifying repository URL(s) to use as the destination
-When you run `mgt`, you typically provide a path to the `repo_file`, eg: `mgt split-out my_repo_file.txt`
+When you run `mgt`, you typically provide a path to the `repo_file`, eg: `mgt split-out my_repo_file.rf`
## Prerequisites
diff --git a/die/Cargo.lock b/die/Cargo.lock
new file mode 100644
index 0000000..7766f6c
--- /dev/null
+++ b/die/Cargo.lock
@@ -0,0 +1,5 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+[[package]]
+name = "die"
+version = "1.0.0"
diff --git a/die/Cargo.toml b/die/Cargo.toml
new file mode 100644
index 0000000..d56bc69
--- /dev/null
+++ b/die/Cargo.toml
@@ -0,0 +1,7 @@
+[package]
+name = "die"
+version = "1.0.0"
+authors = ["nikita-skobov"]
+edition = "2018"
+
+[dependencies]
diff --git a/die/src/lib.rs b/die/src/lib.rs
new file mode 100644
index 0000000..0920bd4
--- /dev/null
+++ b/die/src/lib.rs
@@ -0,0 +1,32 @@
+/// in debug mode, use panic so we get a stack trace
+#[cfg(debug_assertions)]
+#[macro_export]
+macro_rules! die {
+ () => (::std::process::exit(1));
+ ($x:expr; $($y:expr),+) => ({
+ panic!($($y),+);
+ });
+ ($($y:expr),+) => ({
+ panic!($($y),+);
+ });
+}
+
+/// in release mode, use print so its not ugly
+/// Example:
+/// ```
+/// if bad_condition { die!("Oops, the condition was {}", bad_condition)}
+/// ```
+/// if in debug mode, this will panic, otherwise this will println, and then exit 1
+#[cfg(not(debug_assertions))]
+#[macro_export]
+macro_rules! die {
+ () => (::std::process::exit(1));
+ ($x:expr; $($y:expr),+) => ({
+ println!($($y),+);
+ ::std::process::exit($x)
+ });
+ ($($y:expr),+) => ({
+ println!($($y),+);
+ ::std::process::exit(1)
+ });
+}
diff --git a/doc/gen_repo_file.sh b/doc/gen_repo_file.sh
index 02d9b5f..2fb80f7 100755
--- a/doc/gen_repo_file.sh
+++ b/doc/gen_repo_file.sh
@@ -1,4 +1,7 @@
#!/usr/bin/env bash
cat doc/repo_file.template
-./extract-lines.sh src/repo_file.rs "///"
+# temporarily remove this, as the repo_file format has
+# changed, and I removed the doc comments.
+# TODO: add doc comments back to the repo file module
+# ./extract-lines.sh src/repo_file.rs "///"
diff --git a/doc/repo_file.template b/doc/repo_file.template
index 93ff5fc..864c29e 100644
--- a/doc/repo_file.template
+++ b/doc/repo_file.template
@@ -2,40 +2,68 @@
I created `mgt` with the intention of defining `repo_files`
that contain information on how to split out/in local repositories back
-and forth between remote repositories. A `repo_file` is just a text file
-that has variables that describe how your repository should be split. The
-syntax is bash-like and only supports variables, strings, lists of strings,
-and comments.
+and forth between remote repositories. A `repo_file` is a toml file
+that has variables that describe how your repository should be split. There is
+a slight parsing issue with the toml parser which requires that certain sections
+(`include` and `exclude`) to be at least 2 new lines apart from the
+sections before them. Alternatively, these sections can be placed before
+any other section to avoid this issue.
-Here is a commented `repo_file` that explains what some of the common variables do.
+For example:
+
+```toml
+[repo]
+name = "my repo"
+
+include = ["this"]
+```
+
+The above is bad because there is only one newline between `[repo]` and `include`.
+The parser will detect `include` as part of the `[repo]` section. To avoid this issue,
+you can do either:
-For a full documentation on every option, see [here](#repo-file-variables)
+```
+# good:
+[repo]
+name = "my repo"
-```sh
+include = ["this"]
+
+# also good:
+include = ["this"]
+[repo]
+name = "my repo"
+```
+
+Here is a commented `repo_file` that explains what some of the common variables do.
+
+
+```toml
+[repo]
# used for: git pull $remote_repo when doing split-in
# and for split-out if using --rebase or --topbase
-remote_repo="https://github.com/myname/myrepo"
-
+remote = "https://github.com/myname/myrepo"
# instead of pulling remote_repo from HEAD,
# it can pull from a specific branch instead
-remote_branch="feature/X"
-
+branch = "feature/X"
# allows you to specify the name of the branch
# that should be output
-repo_name="git-monorepo-tools"
+name = "git-monorepo-tools"
+
+# (needs 2 empty lines here^ to parse correctly!)
# includes the source repository files/directories
# exactly as is, without changing the paths
# NOTE: directories must have trailing slash
-include=(
+include = [
"doc/some_file.txt"
- "scripts/"
-)
+ "scripts/",
+]
# another example of include:
# include can just be a string:
-include="scripts/"
+include = "scripts/"
# includes the source files/folders into the destination files/folders
# ie: use this variable if you wish to rename paths
@@ -45,29 +73,24 @@ include="scripts/"
# all files/folders other than lib/cool-lib/
# NOTE: to put everything in the root, you must specify a string with a
# single empty space:
-include_as=(
- "lib/cool-lib/" " "
-)
+[include_as]
+"lib/cool-lib/" = " "
+
# Another example of include_as:
# in this example we rename one of the lib files
# and we also move a directory to a different part of the
# destination
-include_as=(
- "lib/get_arg.sh" "lib/get_arg.bsc"
- "repos/my_blog/" "lib/my_blog/"
-)
+[include_as]
+"lib/get_arg.sh" = "lib/get_arg.bsc"
+"repos/my_blog/" = "lib/my_blog/"
+
+# (again, we need 2 empty lines here)
# excludes the source files/folders from
# being included in the destination.
-exclude=(
- "lib/secret_file.txt"
+exclude = [
+ "lib/secret_file.txt",
"old/embarassing/project/"
-)
+]
```
-
-
-# Repo File Variables
-
-The following is a list of all valid variables you can use in your repo file.
-
diff --git a/examples/README.md b/examples/README.md
deleted file mode 100644
index f39e70a..0000000
--- a/examples/README.md
+++ /dev/null
@@ -1,555 +0,0 @@
-# Examples of what `mgt` can do
-
-This is a series of examples that can show what `mgt` can do, and what kinds of problems it was made to solve. This document
-was designed to be read through from start to finish as each step builds on the last. However, if you wish to just skip to the sections
-that are interesting to you, you can use the table of contents below. Otherwise, the first section starts [here.](#purpose)
-
-* [split out a single file](#scenario-01-split-out-file)
-* [split out a subdirectory](#scenario-02-split-out-folder)
-* [multiple includes](#scenario-03-multiple-includes)
-* [include exclude](#scenario-04-include-exclude)
-* [include as root](#scenario-05-include-as-root)
-* [include as rename and move](#scenario-06-include-as-rename-and-move)
-* [split in existing code](#scenario-07-split-in-existing-code)
-* [let mgt rebase for us](#scenario-08-let-mgt-rebase-for-us)
-* [mgt custom rebase algorithm](#scenario-09-topbase)
-
-## Purpose
-
-`git` has a command called `subtree` that can
-
-> Merge subtrees together and split repository into subtrees
-
-This is useful to
-
-- split out a subdirectory of your local repository into an entire new repository
-- split in an entire remote repository into a subdirectory of your local repository
-
-A problem I had with `git subtree` is that you could not specify multiple subdirectories to include,
-and you could not exclude certain parts of the repository that you did not want to include.
-
-`mgt` was originally designed to solve these problems, but has also added a few things on top of that
-functionality to make contributing code back and forth between local and remote repositories easier.
-
-We will show those additional features, and why they are useful in a bit, but for now, let's
-start by showing how to use the basic features of `mgt`.
-
-## Scenario 01 split out file
-
-We will work with a private, local git repository that has the following directory structure:
-
-```
-lib/
-lib/projectA/
-lib/projectA/src/
-lib/projectA/src/test.html
-lib/projectA/src/index.html
-lib/projectA/README.md
-lib/projectA/secret.txt
-lib/projectB/
-lib/projectB/test/
-lib/projectB/test/testfile.js
-lib/projectB/README.md
-lib/projectB/server.js
-lib/projectC/script.sh
-config/
-config/example.conf
-```
-
-Assume every file above was committed on its own. This does not need to be the case when you use `mgt`, but for these examples
-it will make the history easier to conceptualize.
-
-Also assume for all examples that we are in the root of the repository unless specified otherwise.
-
-We want to take a portion of our repository, and share it publically. We don't want to push the whole
-repository because there are parts of it that contain code that we do not want to share.
-
-We will make a `repo_file` (we will name it repo_file.txt and put it
-at the root of our repository) that looks like this:
-
-```sh
-# repo_file.txt
-repo_name="my-projects"
-
-include="lib/projectA/src/test.html"
-```
-
-Note that the `repo_name` variable is the name of the branch that will be created for us by `mgt`.
-
-We set the `include` variable to a single string containing the path (from the root of our local repository)
-to the test.html file in projectA.
-
-If we run:
-
-```sh
-mgt split-out repo_file.txt
-```
-
-`mgt` will create a new branch for us (named my-projects), and checkout to that branch.
-
-The newly created branch will contain the following structure:
-
-```
-lib/projectA/src/test.html
-```
-
-NOTE: we will still have repo_file.txt in the root of our directory
-because we did not commit repo_file.txt, and `mgt` does not modify files that aren't committed.
-
-## Scenario 02 split out folder
-
-Now let's say that we actually changed our mind, and we wanted our new split out repository
-to be a bit different. Since `mgt` only modifies the history of a temporary branch, we can easily go back
-and try again by going back to master
-
-```sh
-git checkout master
-```
-
-We edit our `repo_file` to look like this:
-
-```sh
-repo_name="my-projects"
-
-include="lib/projectA/src/"
-```
-
-Instead of only including the single test.html file, we will now include the entire `lib/projectA/src/` directory.
-NOTE: You must have a trailing slash for directories.
-
-We will run our `split-out` command again:
-
-```sh
-mgt split-out repo_file.txt
-```
-
-**However, this will fail because we already have a branch named `my-projects`**. In this case, `mgt` will not do anything because
-it does not want to override a branch that already exists. It will simply exit with an error message.
-
-We can either:
-1. delete the `my-projects` branch (which is easy and safe to do because in this example we don't want it anyway)
-2. specify an alternate output branch name via the `-o` or `--output-branch`
-
-For this example, let's specify an alternate output branch name:
-
-```sh
-mgt split-out repo_file.txt -o temp-branch
-```
-
-Now, we are in `temp-branch` and our repository structure looks like:
-
-```
-lib/projectA/src/test.html
-lib/projectA/src/index.html
-```
-
-For all future examples we will start from the `master` branch, and with no other branches.
-
-## Scenario 03 multiple includes
-
-Now, let's say we actually want the `README.md` that is in the root of our `projectA/` folder, but we
-**do not** want the `secret.txt` file from showing up in our split out repository. There are 2 ways we can accomplish this.
-The first is by using an explicit list of files/paths to include in our `repo_file`:
-
-```
-repo_name="my-projects"
-
-include=(
- "lib/projectA/src/"
- "lib/projectA/README.md"
-)
-```
-
-The `include` variable is now a list of strings. It can include any number of paths that you wish to include, but here
-we just have two.
-
-After running `mgt split-out repo_file.txt`, our repository structure now looks like:
-
-```
-lib/projectA/src/test.html
-lib/projectA/src/index.html
-lib/projectA/README.md
-```
-
-## Scenario 04 include exclude
-
-To do the same as Scenario 03, we can exclude files/folders via the `exclude` variable. The paths listed in the `exclude` variable
-are excluded **AFTER** the `include` variable. This means that you first include, and then you exclude, so the `exclude` variable
-can remove portions of included paths that you do not want. Consider:
-
-```
-repo_name="my-projects"
-
-include="lib/projectA/"
-exclude="lib/projectA/secret.txt"
-```
-
-Which, after the `mgt split-out repo_file.txt` command will produce a repository structure like:
-
-```
-lib/projectA/src/test.html
-lib/projectA/src/index.html
-lib/projectA/README.md
-```
-
-NOTE: that the `exclude` variable can also be a list of paths like the `include` variable. In this
-example though we only needed a single file to be excluded.
-
-## Scenario 05 include as root
-
-So far we have actually been keeping the repository structure the same, and basically we've just been deleting files that
-we didn't want. `mgt` can also let you rename files/paths, and restructure your repository. Let's consider an example where
-we want our new, split out repository to be in the root (ie: we don't want it to start with `lib/projectA`).
-Here is a `repo_file` that will accomplish that:
-
-```
-repo_name="my-projects"
-
-include_as=(
- "lib/projectA/" " "
-)
-
-exclude="lib/projectA/secret.txt"
-```
-
-We introduce a new variable called `include_as`. `include_as` is always a list of strings, and it must be
-specifically formatted. It needs to be an even-lengthed list where the paths with an even index are the source
-paths, and the paths with an odd index are the destination paths. In this case, we only have one source, and one
-destination. Our source is `lib/projectA/`, and our destination is ` ` a string consisting of a single space.
-The single space string is a special case of the `include_as` variable. When `mgt` sees an `include_as` variable
-that has a single space, it will interpret that as move everything into the root of the repository.
-
-After running `mgt split-out repo_file.txt`, `mgt` will produce a repository structure like:
-
-```
-src/test.html
-src/index.html
-README.md
-```
-
-If you have been paying attention, you might notice something weird going on here. Previously, we learned that
-the excluded paths get excluded after the included paths. If that's true, then wouldn't the `include_as` variable get ran
-first, and that would move `lib/projectA/*` to the root of the repository, and then the `exclude` variable is referencing
-`lib/projectA/secret.txt` which is now located in the root: `secret.txt`. How does this work?
-
-This works because the `include_as` variable gets ran **AFTER** the `exclude` variable. The `include_as` variable is also special because
-we actually need to `include` it regularly first, and then `include_as` after we `exclude`.
-
-`mgt` will take all of the source paths (in this case just one: `lib/projectA/`), and `include` them regularly first. And then `include_as` at the very end
-to rename the path to what it should be.
-
-## Scenario 06 include as rename and move
-
-The above `include_as` was pretty simple, but we can use `include_as` to significantly restructure our repository. Let's say we
-want to move the `test.html` file to the root of the repository, and we want to rename the `src/` directory to `lib/`, we can do that with:
-
-```
-repo_name="my-projects"
-
-include_as=(
- "lib/projectA/src/test.html" "test.html"
- "lib/projectA/src/" "lib/"
- "lib/projectA/" " "
-)
-
-exclude="lib/projectA/secret.txt"
-```
-
-NOTE: For simple `include_as` scenarios, the order that we specified the paths did not really matter. In this case it **does matter**. We have to
-specify the test.html move first because we want to take it out of the `src/` directory before we rename it. We also want the `src/` to `lib/` rename to happen before
-we move the entire `lib/projectA/` into root.
-
-As a good rule of thumb, you should list your `include_as` in order of most specific (furthest depth in the repository), to least specific (closer to the
-root of the repository).
-
-Alternatively, we could have writtenn our `repo_file` more verbosely:
-
-```
-repo_name="my-projects"
-
-include_as=(
- "lib/projectA/src/test.html" "test.html"
- "lib/projectA/src/index.html "lib/index.html"
- "lib/projectA/README.md" "README.md"
-)
-```
-
-This will accomplish the same as the previous `repo_file`, but it has the disadvantage that if we later add files to our
-`lib/projectA` we will have to rewrite our `repo_file`.
-
-
-After running `mgt split-out repo_file.txt`, `mgt` will produce a repository structure like:
-
-```
-lib/index.html
-test.html
-README.md
-```
-
-
-## Scenario 07 split in existing code
-
-Now let's say we are finally happy with the state of our repository, and we are ready to make it public.
-
-We will push it to our remote repository: `https://example.com/my-projects`
-
-We then update our `repo_file` to have the `remote_repo` variable:
-
-```
-remote_repo="https://example.com/my-projects"
-
-include_as=(
- "lib/projectA/src/test.html" "test.html"
- "lib/projectA/src/" "lib/"
- "lib/projectA/" " "
-)
-
-exclude="lib/projectA/secret.txt"
-```
-
-If we run `mgt split-out repo_file.txt` again, it will still work, and still make a `my-projects` branch because that is what
-it detects as the repository name via the end of the `remote_repo` variable.
-
-Now that our code exists in both our local repository, and in a remote repository `https://example.com/my-projects`, **what happens if we, or
-someone else makes changes on the remote repository?**
-
-How can we bring those changes into our local repository? After all, the repositories have entirely different directory structures, and technically
-they have different git histories because `mgt` rewrites the history in order to move/rename/include/exclude paths.
-
-This is where `mgt split-in` helps us.
-
-`mgt split-in` originally was designed to simply take code from some remote source, and put it into our local repository once. However,
-it's functionality can be extended as a way to enable us to receive contributions from the upstream remote repository. Let's try it now:
-
-```sh
-mgt split-in repo_file.txt
-```
-
-This will start by doing the same thing that `split-out` does, mainly it will create a new branch named `my-projects`, but instead of making the new
-branch directly from our current `HEAD`, it will make an entirely empty branch. Then it will pull the most recent `HEAD` of the `remote_repo` into the
-`my-projects` branch. Then, it will rewrite the history of this branch according to the variables we defined in the `repo_file`.
-
-**This is the exciting part: It will do the exact same mapping but in reverse**
-
-This is extremely useful because this enables us to use the same `repo_file` for both `split-out` and `split-in` commands.
-
-After running the above command, we will be on the `my-projects` branch, and our repository structure will look like:
-
-```
-lib/projectA/src/test.html
-lib/projectA/src/index.html
-lib/projectA/README.md
-```
-
-This is the contents of the remote repository remapped to match the structure of our original repository.
-
-## Scenario 07 split in existing code and merging
-
-So far, we have only been looking at the repository structure, but now it will be important to look at the history.
-
-Continuing from where we left off in scenario 06, let's do a git log of our current branch that was just `split-in`:
-
-(remember that originally, we made a single commit for every single file. It doesn't matter how you make your commits, but for the sake of this example it is important to point out so that we can track the commits easier as they are mapped between repositories)
-
-```
-git log my-projects --oneline
-# output:
-9c982ae (HEAD -> my-projects) testhtml
-17076f7 readme
-dd777c1 index.html
-```
-
-Let's also look at the git log for the master branch (which is the original repository)
-
-```
-git log master --oneline
-# output:
-9366b5d (master) secret
-10124ea testhtml
-4cf229f script
-3af8e40 server
-e8cc01e readme
-dbd1b52 testfilejs
-a24ad83 readme
-664977f index.html
-c61b8a2 example.config
-```
-
-There are two things to notice here:
-
-1. the branches don't have a shared root commit
-2. even the commits that are exactly the same (index.html, testhtml) have different hashes
-
-What happens if we try to merge my-projects into master?
-
-Well as it currently stands, we cannot. However, **what we CAN do is rebase our branch onto master, and THEN merge.**
-
-```
-git rebase master
-```
-
-This succeeds, and now if we log our current branch again, we will see:
-
-```
-git log my-projects --oneline
-# output
-9366b5d (HEAD -> my-projects, master) secret
-10124ea testhtml
-4cf229f script
-3af8e40 server
-e8cc01e readme
-dbd1b52 testfilejs
-a24ad83 readme
-664977f index.html
-c61b8a2 example.config
-```
-
-Now the my-projects branch is up to date, and compatible with the master branch. If we want, we can go ahead and merge the branch, but as it stands there is nothing new to merge, because they are both at the same commit. Let's simulate a scenario where there might be commits to merge back into our master.
-
-We go back to master, delete the my-projects branch:
-
-```
-git checkout master
-git branch -D my-projects
-```
-
-Now, in a seperate folder, let's clone the remote repository, make some changes, commit, and push them back up:
-
-```
-cd some-other-folder
-# we add a dot at the end to tell git to clone into our current directory
-git clone https://example.com/my-projects .
-echo "readme append" >> README.md
-git add README.md
-git commit -m "readme append"
-git push origin master
-```
-
-The log of our remote repository now looks like:
-
-```
-1387b95 (HEAD -> master) readme append
-4831d6e testhtml
-a39293a readme
-b22ba3b index.html
-```
-
-
-Now let's go back to our original repository, and see if we can pull the recent changes into our original local repository
-
-```
-cd -
-mgt split-in repo_file.txt
-```
-
-We are now in the my-projects branch, and our history looks like:
-
-```
-4ea76c9 (HEAD -> my-projects) readme append
-9c982ae testhtml
-17076f7 readme
-dd777c1 index.html
-```
-
-Again, it's important to remember that the hashes we see in the my-projects **branch** are different than the hashes we saw
-in the my-projects **repository** because even though these commits modified the same files, and have the same commit messages,
-**the files are located in different places which affects the commit hash**. But that is not important in most cases because `git rebase`
-is smart enough to figure out how to rebase for us. Let's rebase our current my-projects branch onto master and look at the log:
-
-```
-git rebase master
-git log my-projects --oneline
-# output:
-781b749 (HEAD -> my-projects) readme append
-9366b5d (master) secret
-10124ea testhtml
-4cf229f script
-3af8e40 server
-e8cc01e readme
-dbd1b52 testfilejs
-a24ad83 readme
-664977f index.html
-c61b8a2 example.config
-```
-
-This is exactly what we wanted. **We were able to use `mgt split-in` to restructure our remote repository to match the structure of our local repository** and we **used `git rebase` to make the temporary branch compatible with master so that we can merge the recent changes**. We can merge however we want, ie: a ff-merge, make a merge commit, or squash all remote commits into one.
-
-
-## Scenario 08 let mgt rebase for us
-
-Instead of merging the recent commit into master, let's demonstrate an alternate way of doing the previous scenario.
-`mgt` has a convenience option to rebase for us.
-
-We will go back to our local master branch, delete the my-projects branch, and then `split-in` again:
-
-```
-git checkout master
-git branch -D my-projects
-mgt split-in repo_file.txt --rebase
-```
-
-Notice we added an option to the `split-in` command: **`--rebase`**
-
-The `--rebase` option will add a `git rebase ` step after it creates the temporary branch.
-So in this example, the temporary branch is my-projects, and the branch that we started from was master. So
-`mgt` will rebase my-projects onto master. We can do a git log again:
-
-```
-32552fc (HEAD -> my-projects) readme append
-9366b5d (master) secret
-10124ea testhtml
-4cf229f script
-3af8e40 server
-e8cc01e readme
-dbd1b52 testfilejs
-a24ad83 readme
-664977f index.html
-c61b8a2 example.config
-```
-
-Here we see that our temporary branch: my-projects is already rebased on top of master, and is ready to be merged.
-
-For this, and the next example, let's use a squash merge (even though there is only one commit to squash here, but in real world
-uses you will probably be squashing more than one)
-
-```
-git checkout master
-git merge my-projects --squash
-# this leaves us in a staged state, so
-# we are ready to make our squash commit:
-git commit -m "squashed readme changes from remote"
-```
-
-Now our local repository's master git log looks like:
-
-```
-d6a5055 (master) squashed readme changes from remote
-9366b5d secret
-10124ea testhtml
-4cf229f script
-3af8e40 server
-e8cc01e readme
-dbd1b52 testfilejs
-a24ad83 readme
-664977f index.html
-c61b8a2 example.config
-```
-
-## Scenario 09 Topbase
-
-I came up with an alternate rebasing algorithm that is useful to `mgt` in some scenarios. This algorithm is called `topbase`, and what it does is it finds
-an alternate fork point, and then runs an interactive rebase, stopping at that fork point. `git` is good at doing this most of the time, but for complicated repository rewrites, it sometimes struggles, which is why I needed to add this to `mgt`.
-
-The idea of how and when to use `topbase` is if you originally had conflicts when rebasing your repository, but you resolved those conflicts, and then
-pushed those changes. However, the next time you try to run `mgt split-in --rebase`, it still has conflicts because `git` is struggling to find the correct fork point due to the previous conflicts, and due to the fact that the structure of the repository is different. What `topbase` does differently is that **it only tries to rebase to the most common commit**. That means it starts from the head of the branch that is going to be rebased (top), and it goes down in history, one commit at a time, and checks if that commit "exists" in the branch that is going to be the (base) branch. **As soon as it finds a commit that exists in both the top and base branch, it will run the interactive rebase on all prior commits**. `topbase` is able to find matching commits because in `git`, files are hashed into what are called "blobs", and blobs are the same regardless of the name of the file, or where the file is.
-
-`topbase` exists as both an individual command (ie: `mgt topbase `) if you want to topbase branches that already exist in your local repository. And it also exists as an option to pass to `mgt` `split-in` or `split-out`.
-
-In the example we have been working with, our repository structure is simple enough to continue using `--rebase` every time we want to accept updates, but if we wanted to, we could run it like this:
-
-```
-mgt split-in repo_file.txt --topbase
-```
-
-A good rule of thumb is to use `--rebase` the first time you use `mgt split-in` or `mgt split-out`, and then use `--topbase` every time afterwards. You can think of `--topbase` as a "get the most recent updates" option.
-
diff --git a/src/check.rs b/src/check.rs
index 55b5ac1..c53235d 100644
--- a/src/check.rs
+++ b/src/check.rs
@@ -1,7 +1,8 @@
use clap::ArgMatches;
-use super::git_helpers;
-use super::git_helpers::Short;
+use super::git_helpers3;
+use super::git_helpers3::Commit;
+use super::git_helpers3::Oid;
use super::exec_helpers;
use super::split::Runner;
use super::repo_file::RepoFile;
@@ -41,16 +42,11 @@ impl<'a> CheckUpdates for Runner<'a> {
should_clean_fetch_head: bool,
should_summarize: bool,
) -> Self {
- let repo = if let Some(ref r) = self.repo {
- r
- } else {
- die!("Failed to get repo");
- };
// TODO: probably need to add blob_applies_to_repo_file here?
// I think in most cases this isnt necessary, but I should
// try to think of what edge cases this would be needed
let all_upstream_blobs = get_all_blobs_in_branch(upstream_branch);
- let all_commits_of_current = match git_helpers::get_all_commits_from_ref(repo, current_branch) {
+ let all_commits_of_current = match git_helpers3::get_all_commits_from_ref(current_branch) {
Ok(v) => v,
Err(e) => die!("Failed to get all commits! {}", e),
};
@@ -59,10 +55,10 @@ impl<'a> CheckUpdates for Runner<'a> {
let mut commits_to_take = vec![];
let mut commit_summaries = vec![];
- let mut summarize_cb = |c: &git2::Commit| {
+ let mut summarize_cb = |c: &Commit| {
if should_summarize {
- commits_to_take.push(c.id());
- commit_summaries.push(c.summary().unwrap().to_string());
+ commits_to_take.push(c.id.clone());
+ commit_summaries.push(c.summary.clone());
}
};
let should_take_blob_cb = |c: &BlobCheck| {
@@ -83,7 +79,22 @@ impl<'a> CheckUpdates for Runner<'a> {
);
if should_summarize {
- summarize_updates(commits_to_take, commit_summaries);
+ let command_to_take = match self.repo_file_path {
+ None => None,
+ Some(file_path) => {
+ let split_mode = if current_is_remote {
+ "split-in"
+ } else {
+ "split-out"
+ };
+ // TODO: calculate if it can be topbased/rebased/whatever...
+ // here we just assume that it can be topbased...
+ let command_string = "To perform this update you can run: ";
+ let command_string = format!("\n{}\nmgt {} {} --topbase", command_string, split_mode, file_path);
+ Some(command_string)
+ }
+ };
+ summarize_updates(command_to_take, commits_to_take, commit_summaries);
}
if should_clean_fetch_head {
@@ -102,7 +113,8 @@ impl<'a> CheckUpdates for Runner<'a> {
}
pub fn summarize_updates(
- commits_to_take: Vec,
+ command_string: Option,
+ commits_to_take: Vec,
commit_summaries: Vec,
) {
match commits_to_take.len() {
@@ -110,10 +122,13 @@ pub fn summarize_updates(
_ => {
println!("upstream can take {} commit(s) from current:", commits_to_take.len());
for i in 0..commits_to_take.len() {
- let id = commits_to_take[i];
+ let id = &commits_to_take[i];
let summary = &commit_summaries[i];
println!("{} {}", id.short(), summary);
}
+ if let Some(command_string) = command_string {
+ println!("{}", command_string);
+ }
}
}
}
@@ -172,7 +187,7 @@ pub fn blob_path_applies_to_repo_file(
// the other to decide whether or not to take a blob when
// getting all blobs from commit
pub fn topbase_check_alg_with_callback(
- all_commits_of_current: Vec,
+ all_commits_of_current: Vec,
all_upstream_blobs: HashSet,
cb: &mut F,
should_take_blob: Option<&dyn Fn(&BlobCheck) -> Option>,
@@ -181,7 +196,7 @@ pub fn topbase_check_alg_with_callback(
// and not consider it...
should_skip_if_no_blobs: bool,
)
- where F: FnMut(&git2::Commit)
+ where F: FnMut(&Commit)
{
// for every commit in the current branch
// check if every single blob of every commit exists in the upstream branch.
@@ -189,15 +204,14 @@ pub fn topbase_check_alg_with_callback(
// exists in upstream, then we break
for c in all_commits_of_current {
// I think we want to skip merge commits, because thats what git rebase
- // interactive does by default. also, is it safe to assume
- // any commit with > 1 parent is a merge commit?
- if c.parent_count() > 1 {
+ // interactive does by default.
+ if c.is_merge {
continue;
}
let mut current_commit_blobs = HashSet::new();
get_all_blobs_from_commit_with_callback(
- &c.id().to_string()[..],
+ &c.id.long()[..],
&mut current_commit_blobs,
should_take_blob,
);
@@ -225,11 +239,11 @@ pub fn topbase_check_alg_with_callback(
pub fn topbase_check_alg(
- all_commits_of_current: Vec,
+ all_commits_of_current: Vec,
all_upstream_blobs: HashSet,
cb: &mut F
)
- where F: FnMut(&git2::Commit),
+ where F: FnMut(&Commit),
{
topbase_check_alg_with_callback(
all_commits_of_current,
@@ -443,7 +457,7 @@ pub fn run_check(matches: &ArgMatches) {
}
for file in files_to_check {
- println!("--- Checking {}", file);
+ println!("---\nChecking {}", file);
let mut runner = Runner::new(matches);
runner.repo_file_path = Some(file.as_str());
let runner = runner.save_current_dir()
diff --git a/src/commands.rs b/src/commands.rs
index 464da38..93be484 100644
--- a/src/commands.rs
+++ b/src/commands.rs
@@ -27,6 +27,8 @@ pub const REMOTE_BRANCH_ARG: [&'static str; 2] = ["remote-branch", "b"];
pub const LOCAL_BRANCH_ARG: &'static str = "local-branch";
pub const RECURSIVE_ARG: [&'static str; 2] = ["recursive", "r"];
pub const ALL_ARG: [&'static str; 2] = ["all", "a"];
+pub const NUM_COMMITS_ARG: &'static str = "num-commits";
+pub const GEN_REPO_FILE_ARG: [&'static str; 2] = ["gen-repo-file", "g"];
const SPLIT_IN_STR: &'static str = "split-in";
const SPLIT_IN_AS_STR: &'static str = "split-in-as";
@@ -51,6 +53,8 @@ const LOCAL_ARG_DESCRIPTION: &'static str = "check if the local branch has commi
const REMOTE_ARG_DESCRIPTION: &'static str = "check if the remote has commits not present in this local branch. This is the default";
const REMOTE_BRANCH_ARG_DESCRIPTION: &'static str = "check updates to/from a specific remote branch instead of what's in the repo file";
const LOCAL_BRANCH_ARG_DESCRIPTION: &'static str = "check updates to/from a specific local branch instead of the current HEAD";
+const NUM_COMMITS_ARG_DESCRIPTION: &'static str = "when pulling from remote, limit to n commits from the current tip. This is probably only useful the first time you do a split-in";
+const GEN_REPO_FILE_ARG_DESCRIPTION: &'static str = "generate a repo file from the provided remote repo and the --as argument gets mapped to [include_as]";
#[derive(Clone)]
pub enum CommandName {
@@ -166,6 +170,13 @@ pub fn split_in<'a, 'b>() -> App<'a, 'b> {
.value_name(INPUT_BRANCH_NAME)
.help("split in from a local branch in this repository")
)
+ .arg(
+ Arg::with_name(NUM_COMMITS_ARG)
+ .long(NUM_COMMITS_ARG)
+ .takes_value(true)
+ .value_name("n")
+ .help(NUM_COMMITS_ARG_DESCRIPTION)
+ )
}
pub fn split_in_as<'a, 'b>() -> App<'a, 'b> {
@@ -225,6 +236,19 @@ pub fn split_in_as<'a, 'b>() -> App<'a, 'b> {
.takes_value(true)
.value_name(OUTPUT_BRANCH_NAME)
.help("name of branch that will be created with new split history")
+ )
+ .arg(
+ Arg::with_name(GEN_REPO_FILE_ARG[0])
+ .long(GEN_REPO_FILE_ARG[0])
+ .short(GEN_REPO_FILE_ARG[1])
+ .help(GEN_REPO_FILE_ARG_DESCRIPTION)
+ )
+ .arg(
+ Arg::with_name(NUM_COMMITS_ARG)
+ .long(NUM_COMMITS_ARG)
+ .takes_value(true)
+ .value_name("n")
+ .help(NUM_COMMITS_ARG_DESCRIPTION)
);
}
diff --git a/src/exec_helpers.rs b/src/exec_helpers.rs
index fe9f480..3f632db 100644
--- a/src/exec_helpers.rs
+++ b/src/exec_helpers.rs
@@ -14,6 +14,18 @@ pub fn executed_successfully(exe_and_args: &[&str]) -> bool {
}
}
+/// useful when you only care if an execution yielded an error
+/// if the return is None, you know it was successful
+pub fn executed_with_error(exe_and_args: &[&str]) -> Option {
+ match execute(exe_and_args) {
+ Err(e) => Some(format!("{}", e)),
+ Ok(o) => match o.status {
+ 0 => None,
+ _ => Some(o.stderr.lines().next().unwrap().to_string()),
+ },
+ }
+}
+
pub fn execute_with_env(
exe_and_args: &[&str],
keys: &[&str],
diff --git a/src/git_helpers.rs b/src/git_helpers.rs
deleted file mode 100644
index 202707f..0000000
--- a/src/git_helpers.rs
+++ /dev/null
@@ -1,559 +0,0 @@
-use git2::Repository;
-use git2::Error;
-use std::path::PathBuf;
-use std::path::Path;
-use std::fs;
-use std::str::from_utf8;
-use super::die;
-
-pub trait Short {
- fn short(self) -> String;
-}
-impl Short for git2::Oid {
- fn short(self) -> String {
- self.to_string().get(0..7).unwrap().into()
- }
-}
-
-
-fn remove_git_from_path_buf(pathbuf: &mut PathBuf) -> PathBuf {
- match &pathbuf.file_name() {
- Some(p) => {
- match p.to_str() {
- Some(s) => {
- if s == ".git" {
- pathbuf.pop();
- }
- },
- _ => (),
- }
- },
- _ => (),
- };
- return pathbuf.clone();
-}
-
-pub fn get_repository_and_root_directory(dir: &PathBuf) -> (Repository, PathBuf) {
- let repo = match Repository::discover(dir) {
- Err(e) => die!("Failed to find or open repository from {} - {}", dir.display(), e),
- Ok(repo) => repo,
- };
-
- let pathbuf = remove_git_from_path_buf(
- &mut repo.path().to_path_buf()
- );
-
- return (repo, pathbuf);
-}
-
-
-pub fn get_number_of_commits_in_branch(
- branch_name: &str, repo: &Repository
-) -> Result {
- let mut revwalk = repo.revwalk()?;
- revwalk.set_sorting(git2::Sort::NONE)?;
- let latest_commit_id = repo.revparse_single(branch_name)?.id();
- revwalk.push(latest_commit_id)?;
-
- let mut num_commits = 0;
- for _ in revwalk {
- num_commits += 1;
- }
-
- return Ok(num_commits);
-}
-
-pub fn branch_exists(
- branch_name: &str,
- repo: &Repository,
-) -> bool {
- let local_branch = git2::BranchType::Local;
- match repo.find_branch(branch_name, local_branch) {
- Ok(_) => true,
- _ => false,
- }
-}
-
-pub fn get_current_branch(
- repo: &Repository
-) -> Result {
- let reference = match repo.head() {
- Ok(refrnc) => refrnc,
- Err(e) => {
- let msg: String = e.message().into();
- if ! msg.contains("' not found") {
- // some message we dont care about
- // so just return the error as usual
- return Err(e);
- }
- // if it says "ref 'refs/something' not found"
- // then it exists, its just not a valid head,
- // so we want to return that anyway
- return Ok(msg.split("'").skip(1).take(1).collect::>()[0].into());
- },
- };
-
- match reference.name() {
- Some(name) => Ok(name.to_string()),
- None => {
- let code = git2::ErrorCode::GenericError;
- let class = git2::ErrorClass::None;
- let message = format!("Cannot get current HEAD reference name. It is probably a malformed UTF-8 issue");
-
- return Err(Error::new(code, class, message));
- }
- }
-}
-
-// branch_name should just be the branch name,
-// it should not include refs/heads/*
-// when this method calls checkout_to_branch, it will format it
-pub fn make_orphan_branch_and_checkout(
- branch_name: &str,
- repo: &Repository,
-) -> Result<(), git2::Error> {
- if branch_exists(branch_name, repo) {
- let code = git2::ErrorCode::Exists;
- let class = git2::ErrorClass::Checkout;
- let message = format!("Cannot checkout to orphan branch: {}. It already exists", branch_name);
-
- return Err(Error::new(code, class, message));
- }
-
- let branch_ref = format!("refs/heads/{}", branch_name);
- checkout_to_branch(branch_ref.as_str(), repo)
-}
-
-pub fn get_current_ref(repo: &git2::Repository) -> Option {
- match get_current_branch(repo) {
- Err(_) => None,
- Ok(s) => Some(s),
- }
-}
-
-pub fn checkout_to_branch(
- branch_name: &str,
- repo: &Repository,
-) -> Result<(), git2::Error> {
- repo.set_head(branch_name)
-}
-
-pub fn delete_branch(
- branch_name: &str,
- repo: &Repository,
-) -> Result<(), git2::Error> {
- let local_branch = git2::BranchType::Local;
- let mut branch = repo.find_branch(branch_name, local_branch)?;
- branch.delete()
-}
-
-pub fn checkout_to_branch_and_clear_index(
- branch_name: &str,
- repo: &Repository,
-) -> Result<(), git2::Error> {
- repo.set_head(branch_name)?;
- let mut checkout_opts = git2::build::CheckoutBuilder::new();
- let mut checkout_opts = checkout_opts.recreate_missing(true);
- let mut checkout_opts = checkout_opts.force();
- repo.checkout_head(Some(&mut checkout_opts))
-}
-
-pub fn get_head_commit(
- repo: &Repository,
-) -> Result {
- let current_ref = get_current_branch(repo)?;
- let current_oid = repo.refname_to_id(current_ref.as_str())?;
- repo.find_commit(current_oid)
-}
-
-pub fn list_everything_under_tree(
- repo: &Repository,
- tree: git2::Tree,
- indent: &str,
-) -> Result<(), git2::Error> {
- for t in tree.iter() {
- println!("{}T:{} ({})", indent, t.id(), t.name().unwrap());
- let t_obj = t.to_object(repo)?;
- match t_obj.kind().unwrap() {
- git2::ObjectType::Blob => {
- let blob = match t_obj.into_blob() {
- Ok(b) => b,
- _ => die!("failed to turn into blob"),
- };
- println!("{} B:{}", indent, blob.id());
- }
- git2::ObjectType::Commit => {
- }
- git2::ObjectType::Tree => {
- let t_next = match t_obj.into_tree() {
- Ok(tn) => tn,
- _ => die!("failed to turn into tree"),
- };
- let next_indent = format!("{} ", indent);
- list_everything_under_tree(repo, t_next, next_indent.as_str())?;
- }
- git2::ObjectType::Any => {
- }
- git2::ObjectType::Tag => {
- }
- };
- }
-
- Ok(())
-}
-
-pub fn list_everything_under_commit(
- repo: &Repository,
- commit: git2::Commit,
-) -> Result<(), git2::Error> {
- println!("C:{}", commit.id());
- let tree = commit.tree()?;
- list_everything_under_tree(repo, tree, " ")?;
-
- for p in commit.parents() {
- list_everything_under_commit(repo, p)?;
- }
-
- Ok(())
-}
-
-pub fn get_commit_from_ref<'a>(
- repo: &'a Repository,
- refname: &str,
-) -> Result, git2::Error> {
- let reference = repo.find_reference(refname)?;
- repo.reference_to_annotated_commit(&reference)
-}
-
-pub fn get_all_commits_from_ref<'a>(
- repo: &'a Repository,
- refname: &str,
-) -> Result>, git2::Error> {
- let mut commits = vec![];
-
- let mut revwalk = repo.revwalk()?;
- revwalk.set_sorting(git2::Sort::NONE)?;
- let latest_commit_id = repo.revparse_single(refname)?.id();
- revwalk.push(latest_commit_id)?;
-
- for c in revwalk {
- let cmt = get_commit_from_oid(repo, c.unwrap())?;
- commits.push(cmt);
- }
-
- Ok(commits)
-}
-
-pub fn get_plain_commit_from_ref<'a>(
- repo: &'a Repository,
- refname: &str,
-) -> Result, git2::Error> {
- let reference = repo.find_reference(refname)?;
- match reference.peel_to_commit() {
- Ok(cmt) => Ok(cmt),
- Err(e) => Err(e),
- }
-}
-
-pub fn get_commit_from_oid(
- repo: &Repository,
- oid: git2::Oid,
-) -> Result {
- repo.find_commit(oid)
-}
-
-pub fn rebase_each(
- repo: &Repository,
- rebase: &mut git2::Rebase,
-) -> Result<(), git2::Error> {
- for _ in 0..rebase.len() {
- let rebase_op_option = rebase.next();
- if let None = rebase_op_option {
- // the next was empty, therefore break out
- // to avoid calling next on a rebase operation that should
- // already be done
- break;
- }
- // we can unwrap safely because the None case
- // was handled above
- let op = rebase_op_option.unwrap();
- match op {
- Ok(rebase_op) => {
- let commit = get_commit_from_oid(&repo, rebase_op.id())?;
- let author = commit.author();
- // here we can rewrite the commit if we want
- // by choosing the commit author as the signature
- // we ensure that the hash stays the same in the
- // fast-forwardable case
- rebase.commit(None, &author, None)?;
- }
- Err(e) => return Err(e),
- };
- }
-
- Ok(())
-}
-
-// example:
-// A - B
-// \
-// --C
-// if you want to get:
-// A - B - C*
-// you would do:
-// upstream: B, rebase_from C
-// note: the branch strings must be refs:
-// refs/heads/B and refs/heads/C
-pub fn rebase(
- repo: &Repository,
- // the branch that will be used as the "bottom" of the rebase comparison
- upstream_branch: &str,
- // the branch that will be used as the "top" of the rebase comparison
- rebase_from_branch: &str,
- rebase_opts: Option<&mut git2::RebaseOptions<'_>>,
-) -> Result<(), git2::Error> {
- let upstream = get_commit_from_ref(&repo, upstream_branch)?;
- let branch = get_commit_from_ref(&repo, rebase_from_branch)?;
-
- // initialize
- let mut rebase = match repo.rebase(Some(&branch), Some(&upstream), None, rebase_opts) {
- Ok(r) => r,
- Err(e) => {
- return Err(e);
- },
- };
-
- // do the rebase
- if let Err(e) = rebase_each(repo, &mut rebase) {
- rebase.abort()?;
- return Err(e);
- }
-
- // finalize
- rebase.finish(None)?;
-
- Ok(())
-}
-
-pub fn make_new_branch_from_head(
- repo: &Repository,
- branch_name: &str,
-) -> Result<(), git2::Error> {
- let current_head_commit = get_head_commit(repo)?;
- let branch = repo.branch(branch_name, ¤t_head_commit, false)?;
- Ok(())
-}
-
-// basically git rm -rf .
-// it gets all paths from the index
-// and then removes all of them one by one
-pub fn remove_index_and_files(
- repo: &Repository
-) -> Result<(), git2::Error> {
- let mut index = repo.index().expect("FAILED TO GET INDEX");
- let mut files_to_delete: Vec = vec![];
- for entry in index.iter() {
- let p = from_utf8(&entry.path).unwrap();
- files_to_delete.push(p.into());
- }
- // we probably want to write index before
- // deleting the files, because if the index change fails
- // we dont want to delete the files
- index.clear()?;
- index.write()?;
-
- // we only check if we have successfully removed the first file
- // otherwise whats the point of erroring if we remove one or more files
- // but fail on another one?
- let mut file_removed = false;
- for f in &files_to_delete {
- if ! file_removed {
- let result = fs::remove_file(f);
- if result.is_err() {
- die!("Failed to remove file {}. Stopping operation without modifying index", f.display());
- }
- file_removed = true;
- } else {
- fs::remove_file(f);
- }
- }
-
- // we need to do this to delete empty directories that were left over
- // from deleting the above files... yeah kinda slow but idk a better way
- for f in &files_to_delete {
- let mut parent = f.parent();
- while parent.is_some() {
- let parent_path = parent.unwrap();
- if parent_path.is_dir() {
- // we dont care if this errors.
- // an error will occur if the directory is not empty, which
- // is fine because if its not empty we dont want to delete it anyway
- fs::remove_dir(parent_path);
- }
- parent = parent_path.parent();
- }
- }
-
- Ok(())
-}
-
-pub fn fast_forward(
- repo: &Repository,
- target_reference: &mut git2::Reference,
- source_commit: &git2::AnnotatedCommit,
-) -> Result<(), git2::Error> {
- let name = match target_reference.name() {
- Some(s) => s.to_string(),
- None => String::from_utf8_lossy(target_reference.name_bytes()).to_string(),
- };
- target_reference.set_target(source_commit.id(), "")?;
- repo.set_head(&name)?;
- repo.checkout_head(Some(
- git2::build::CheckoutBuilder::default().force(),
- ))?;
- Ok(())
-}
-
-// given a source commit, merge it into the target branch via the
-// branch name. If target branch is None, use current HEAD instead.
-pub fn merge<'a>(
- repo: &Repository,
- source_commit: git2::AnnotatedCommit<'a>,
- target_branch: Option<&str>,
-) -> Result<(), git2::Error> {
- let analysis = repo.merge_analysis(&[&source_commit])?;
- let refname = match target_branch {
- Some(s) => format!("refs/heads/{}", s),
- None => get_current_branch(repo).unwrap(),
- };
-
- if analysis.0.is_fast_forward() {
- match repo.find_reference(&refname) {
- Ok(mut r) => { fast_forward(repo, &mut r, &source_commit)?; },
- Err(_) => {
- // The branch doesn't exist so just set the reference to the
- // commit directly. Usually this is because you are pulling
- // into an empty repository.
- repo.reference(&refname, source_commit.id(), true, "")?;
- repo.set_head(&refname)?;
- repo.checkout_head(Some(
- git2::build::CheckoutBuilder::default()
- .allow_conflicts(true)
- .conflict_style_merge(true)
- .force(),
- ))?;
- }
- }
- } else {
- die!("cannot fast-forward. Alternate merge strategies not implements yet");
- }
-
- Ok(())
-}
-
-pub fn merge_branches(
- repo: &Repository,
- source_branch: &str,
- target_branch: Option<&str>,
-) -> Result<(), git2::Error> {
- let refname = format!("refs/heads/{}", source_branch);
- let source_ref = repo.find_reference(&refname[..])?;
- let source_commit = repo.reference_to_annotated_commit(&source_ref)?;
- merge(repo, source_commit, target_branch)
-}
-
-pub fn pull(
- repo: &Repository,
- remote_name: &str,
- remote_branch_name: Option<&str>,
-) -> Result<(), git2::Error> {
- let mut remote = repo.remote_anonymous(remote_name)?;
- let remote_branch = remote_branch_name.unwrap_or("master");
- let fetched_commit = fetch(
- repo,
- &[remote_branch],
- &mut remote,
- )?;
- merge(repo, fetched_commit, None)
-}
-
-pub fn fetch<'a>(
- repo: &'a Repository,
- refs: &[&str],
- remote: &'a mut git2::Remote,
-) -> Result, git2::Error> {
- remote.fetch(
- refs,
- Some(&mut git2::FetchOptions::new()
- .download_tags(git2::AutotagOption::None)
- ),
- None
- )?;
-
- let fetched_commit = repo.find_reference("FETCH_HEAD")?;
- Ok(repo.reference_to_annotated_commit(&fetched_commit)?)
-}
-
-pub fn make_new_branch_from_head_and_checkout(
- repo: &Repository,
- branch_name: &str
-) -> Result<(), git2::Error> {
- make_new_branch_from_head(repo, branch_name)?;
- let branch_ref = format!("refs/heads/{}", branch_name);
- checkout_to_branch(branch_ref.as_str(), repo)?;
- Ok(())
-}
-
-#[cfg(test)]
-mod test {
- use super::*;
-
- // #[test]
- // // TODO: write this test
- // // ensure branch exists,
- // // and points to same commit as master. idk how to do that yet
- // fn make_new_branch_works() {
- // let mut pathbuf = PathBuf::new();
- // pathbuf.push(".");
- // let (repo, _) = get_repository_and_root_directory(&pathbuf);
- // make_new_branch_from_head_and_checkout(&repo, "testbranch");
- // }
-
- #[test]
- #[cfg_attr(not(feature = "gittests"), ignore)]
- fn get_number_of_commits_works() {
- let mut pathbuf = PathBuf::new();
- pathbuf.push(".");
- let (repo, _) = get_repository_and_root_directory(&pathbuf);
- let num_commits = get_number_of_commits_in_branch("master", &repo);
- let num = num_commits.unwrap_or(0);
- println!("num: {}", num);
- assert!(num >= 10 && num <= 99999);
- }
-
- #[test]
- #[cfg_attr(not(feature = "gittests"), ignore)]
- fn make_orphan_branch_and_checkout_works() {
- let mut pathbuf = PathBuf::new();
- pathbuf.push(".");
- let (repo, _) = get_repository_and_root_directory(&pathbuf);
- let testbranchname = "refs/heads/blahbranchblaaaah";
- // make sure the branch name doesnt exist yet
- assert_eq!(branch_exists(testbranchname, &repo), false);
- let res = make_orphan_branch_and_checkout(testbranchname, &repo);
- assert_eq!(res.is_ok(), true);
- // current head should point to blahbranch:
- assert_eq!(get_current_branch(&repo).unwrap(), testbranchname.to_string());
- // checkout back to master because thats what the other tests depend on
- checkout_to_branch("refs/heads/master", &repo);
- }
-
- #[test]
- #[cfg_attr(not(feature = "gittests"), ignore)]
- fn get_current_branch_works() {
- let mut pathbuf = PathBuf::new();
- pathbuf.push(".");
- let (repo, _) = get_repository_and_root_directory(&pathbuf);
- assert_eq!(get_current_branch(&repo).unwrap(), "refs/heads/master".to_string());
- }
-}
diff --git a/src/git_helpers3.rs b/src/git_helpers3.rs
new file mode 100644
index 0000000..827a034
--- /dev/null
+++ b/src/git_helpers3.rs
@@ -0,0 +1,254 @@
+/// For the v3 version I rewrote the git_helpers module to interface
+/// with git via the CLI instead of libgit2
+
+use super::exec_helpers;
+
+#[derive(Debug, Clone)]
+pub struct Oid {
+ pub hash: String,
+}
+impl Oid {
+ /// it is assumed that short() will not be called
+ /// on an empty oid
+ pub fn short(&self) -> &str {
+ let substr = self.hash.get(0..7);
+ substr.unwrap()
+ }
+ pub fn long(&self) -> &String {
+ &self.hash
+ }
+}
+#[derive(Debug)]
+pub struct Commit {
+ pub id: Oid,
+ pub summary: String,
+ pub is_merge: bool,
+}
+
+
+pub fn pull(
+ remote_name: &str,
+ remote_branch_name: Option<&str>,
+ num_commits: Option,
+) -> Result<(), String> {
+ let mut exec_args = vec![
+ "git", "pull",
+ remote_name,
+ remote_branch_name.unwrap_or("HEAD"),
+ ];
+
+ let mut _depth_string = String::from("");
+ if let Some(n) = num_commits {
+ _depth_string = format!("--depth={}", n);
+ exec_args.push(_depth_string.as_str());
+ }
+
+ match exec_helpers::executed_with_error(&exec_args) {
+ None => Ok(()),
+ Some(e) => Err(e),
+ }
+}
+
+/// target is the current branch
+pub fn merge_branch(
+ source_branch: &str,
+) -> Result<(), String> {
+ let exec_args = vec![
+ "git", "merge",
+ source_branch
+ ];
+ match exec_helpers::executed_with_error(&exec_args) {
+ None => Ok(()),
+ Some(e) => Err(e),
+ }
+}
+
+pub fn make_orphan_branch_and_checkout(
+ orphan_branch_name: &str
+) -> Result<(), String> {
+ let exec_args = vec![
+ "git", "checkout",
+ "--orphan", orphan_branch_name,
+ ];
+ match exec_helpers::executed_with_error(&exec_args) {
+ None => Ok(()),
+ Some(e) => Err(e),
+ }
+}
+
+/// after checking out an orphan branch, gits index
+/// will be full of files that exist on the filesystem,
+/// and git says they are ready to be added. We want
+/// to tell git to delete these files (which is safe to do because
+/// they exist in another branch)
+pub fn remove_index_and_files() -> Result<(), String> {
+ let exec_args = ["git", "rm", "-rf", "."];
+ let success = exec_helpers::executed_successfully(&exec_args);
+ match success {
+ true => Ok(()),
+ false => Err("Failed to git rm -rf .".into()),
+ }
+}
+
+pub fn branch_exists(branch_name: &str) -> bool {
+ let branch_ref = format!("refs/heads/{}", branch_name);
+ let exec_args = [
+ "git", "show-ref", "--verify", "--quiet", branch_ref.as_str()
+ ];
+ // will return 0 (true) if branch exists , 1 (false) otherwise
+ exec_helpers::executed_successfully(&exec_args)
+}
+
+pub fn delete_branch(
+ branch_name: &str
+) -> Result<(), String> {
+ let exec_args = [
+ "git", "branch", "-D", branch_name,
+ ];
+ match exec_helpers::executed_with_error(&exec_args) {
+ None => Ok(()),
+ Some(e) => Err(e),
+ }
+}
+
+pub fn checkout_branch(
+ branch_name: &str,
+ make_new: bool,
+) -> Result<(), String> {
+ let mut exec_args = vec![
+ "git", "checkout"
+ ];
+ if make_new {
+ exec_args.push("-b");
+ exec_args.push(branch_name);
+ } else {
+ exec_args.push(branch_name);
+ }
+
+ match exec_helpers::executed_with_error(&exec_args) {
+ None => Ok(()),
+ Some(e) => Err(e),
+ }
+}
+
+pub fn get_current_ref() -> Result {
+ let exec_args = [
+ "git", "rev-parse", "--abbrev-ref", "HEAD"
+ ];
+ match exec_helpers::execute(&exec_args) {
+ Ok(out) => {
+ if out.status == 0 {
+ // dont want trailing new line
+ Ok(out.stdout.trim_end().into())
+ } else {
+ Err(out.stderr)
+ }
+ }
+ Err(e) => Err(e.to_string()),
+ }
+}
+
+pub fn get_all_commits_from_ref(
+ refname: &str
+) -> Result, String> {
+ // TODO: in the future might want more info than
+ // just the hash and summary
+ let exec_args = [
+ "git", "log", refname, "--format=%H [%p] %s",
+ ];
+ let mut commits = vec![];
+ let out_str = match exec_helpers::execute(&exec_args) {
+ Err(e) => return Err(e.to_string()),
+ Ok(out) => match out.status {
+ 0 => out.stdout,
+ _ => return Err(out.stderr),
+ }
+ };
+
+ for line in out_str.lines() {
+ // everything before first space is
+ // the commit hash. everything after is the summary
+ let mut line_split = line.split(" ");
+ let hash = line_split.nth(0);
+ let hash = if let Some(h) = hash {
+ h.to_string()
+ } else {
+ return Err("Failed to parse hash".into());
+ };
+ // after we took the hash, we now have
+ // something like [parent, parent, ...]
+ // if there is only one parent, it will be of form
+ // [parent], so we check if this commit is a merge
+ // or not
+ let is_merge = match line_split.next() {
+ None => false,
+ Some(s) => !s.contains(']')
+ };
+ // if we did find a merge, that means we have to
+ // advance our line split until we have reached
+ // the end of the [parent, parent, ...] list
+ if is_merge { loop {
+ match line_split.next() {
+ None => (),
+ Some(s) => if s.contains(']') {
+ break;
+ }
+ }
+ }}
+
+ let summary = line_split.collect::>().join(" ");
+ commits.push(Commit {
+ summary: summary,
+ id: Oid { hash },
+ is_merge,
+ });
+ }
+
+ Ok(commits)
+}
+
+pub fn get_repo_root() -> Result {
+ let exec_args = [
+ "git", "rev-parse", "--show-toplevel",
+ ];
+ match exec_helpers::execute(&exec_args) {
+ Ok(out) => {
+ if out.status == 0 {
+ // dont want trailing new line
+ Ok(out.stdout.trim_end().into())
+ } else {
+ Err(out.stderr)
+ }
+ }
+ Err(e) => Err(e.to_string()),
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn oid_short_and_long_works() {
+ let oid_str = "692ec5536e98fecfb3dfbce61a5d89af5f2eee34";
+ let oid = Oid {
+ hash: oid_str.into(),
+ };
+ let oid_short = oid.short();
+ assert_eq!(oid_short, "692ec55");
+ let oid_long = oid.long();
+ assert_eq!(oid_long, oid_str);
+ }
+
+ // just see if it panics or not :shrug:
+ #[test]
+ #[cfg_attr(not(feature = "gittests"), ignore)]
+ fn get_all_commits_from_ref_works() {
+ let data = get_all_commits_from_ref("HEAD");
+ assert!(data.is_ok());
+ let data = data.unwrap();
+ // this only passes if the test is running from
+ // a git repository with more than 1 commit...
+ assert!(data.len() > 1);
+ }
+}
diff --git a/src/main.rs b/src/main.rs
index 4305120..e35b404 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,4 +1,5 @@
use clap::{App, ArgMatches};
+use die::*;
mod commands;
mod repo_file;
@@ -7,8 +8,8 @@ mod split_in;
mod topbase;
mod check;
mod split;
-mod git_helpers;
mod exec_helpers;
+mod git_helpers3;
fn get_cli_input<'a>() -> ArgMatches<'a> {
let version_str = format!(
@@ -33,35 +34,6 @@ fn get_cli_input<'a>() -> ArgMatches<'a> {
return base_app.get_matches();
}
-// in debug mode, use panic so we get a stack trace
-#[cfg(debug_assertions)]
-#[macro_export]
-macro_rules! die {
- () => (::std::process::exit(1));
- ($x:expr; $($y:expr),+) => ({
- panic!($($y),+);
- });
- ($($y:expr),+) => ({
- panic!($($y),+);
- });
-}
-
-// in release mode, use print so its not ugly
-#[cfg(not(debug_assertions))]
-#[macro_export]
-macro_rules! die {
- () => (::std::process::exit(1));
- ($x:expr; $($y:expr),+) => ({
- println!($($y),+);
- ::std::process::exit($x)
- });
- ($($y:expr),+) => ({
- println!($($y),+);
- ::std::process::exit(1)
- });
-}
-
-
fn main() {
let matches = get_cli_input();
diff --git a/src/repo_file.rs b/src/repo_file.rs
index 53c6ff2..9adb093 100644
--- a/src/repo_file.rs
+++ b/src/repo_file.rs
@@ -1,447 +1,446 @@
use std::path::Path;
use std::fs::File;
use std::io::{BufRead, BufReader};
+use toml::Value;
use super::die;
-#[derive(Debug, PartialEq)]
+#[derive(Debug, PartialEq, Default)]
pub struct RepoFile {
- /// ## repo_name
- /// The name of the remote repository
- /// This will be the branch name when mgt creates a temporary branch.
- /// Required only if `remote_repo` is not specified.
pub repo_name: Option,
- /// ## remote_repo
- /// A valid git repo uri. Can be a local file location, remote url, ssh url, etc.
- /// For `split-in` the git history of `remote_repo` is rewritten to match this local repo's history.
- /// For `split-out` the git history of this local repository is rewritten to match the `remote_repo`.
- /// Required for `split-in`, only required for `split-out` if using `--topbase` or `--rebase`.
pub remote_repo: Option,
- /// ## remote_branch
- /// A name of a branch available on the `remote_repo`. By default `split-in` (and `split-out` if
- /// using `--topbase` or `--rebase`) use the HEAD of the `remote_repo`, but you can specify a specific
- /// branch to use instead.
- /// Optional.
pub remote_branch: Option,
- /// ## include_as
- /// A list of paths where even-indexed paths are the sources, and odd-indexed paths are the destinations.
- /// The source is a path to a file/folder in this local repository, and the destination is
- /// a path to a file/folder in the remote repository.
- /// This is so that you can use the same `repo_file` for both splitting in and out.
- ///
- /// Examples:
- /// ```
- /// include_as=("my_markdown_notes/002-notes-on-this-thing.md" "README.md")
- /// ```
- /// When running `split-out` this will rewrite the users repository
- /// and only keep the file: `my_markdown_notes/002-notes-on-this-thing.md`, however
- /// when it rewrites the history, it will also rename the file to be `README.md`.
- /// When running `split-in` this will take the `README.md` file from the `remote_repo`
- /// and rename it to be `my_markdown_notes/002-notes-on-this-thing.md`
- /// ```
- /// include_as=(
- /// "lib/file1.txt" "file1.txt"
- /// "lib/project/" " "
- /// )
- /// ```
- /// For `split-out` this will rename the local repository's `lib/file1.txt` to just `file1.txt`
- /// and it will take the entire folder `lib/project/` and make that the root of the split out repository.
- /// NOTE that when specifying directories, you MUST include a trailing slash. And if you wish to make a subdirectory
- /// the root of the split repository, the correct syntax is a single empty space: `" "`.
pub include_as: Option>,
- /// ## include
- /// A list of paths to include. Unlike `include_as`, this does not allow for renaming.
- /// There is no source/destination here, it is just a list of paths to keep exactly as they are.
- ///
- /// Examples:
- /// ```
- /// include=(
- /// "README.md"
- /// "LICENSE"
- /// )
- /// ```
- /// This will only take the `README.md` and `LICENSE` files at the root level, and ignore everything else.
- /// ```
- /// include="lib/"
- /// include=("lib/")
- /// ```
- /// Both of the above are valid. `include` can be a single string if you only have one path to include.
pub include: Option>,
- /// ## exclude
- /// A list of paths to exclude. This is useful if you want a folder, but don't want some of the
- /// subfolders.
- ///
- /// Examples:
- /// ```
- /// include="lib/"
- /// exclude=("lib/private/" "lib/README.md")
- /// ```
- /// For `split-in` this will take the entirety of the `lib/` folder, but will not take `lib/README.md` and
- /// will not take the entire subfolder `lib/private/`. Note that `exclude` does not make sense for both `split-out`
- /// and `split-in`. In the above example, if you use this same `repo_file` again to `split-out` your changes,
- /// you do not have a `lib/private` or a `lib/README.md`, so this `exclude` statement will not do anything.
- /// This means you can specify both local paths to exclude and remote paths to exclude:
- /// ```
- /// exclude=(
- /// "localfile.txt"
- /// "remotefile.txt"
- /// )
- /// ```
- /// If your local repository has a `localfile.txt` then `split-out` will not include it, and `split-out` will do
- /// nothing about the `remotefile.txt` (because there isn't one).
- /// If the remote repository has a `remotefile.txt` then that file will be excluded when running `split-in`.
- /// NOTE: in the future there might be an `exclude_local` and `exclude_remote` to avoid these ambiguities.
pub exclude: Option>,
}
impl RepoFile {
pub fn new() -> RepoFile {
- RepoFile {
- repo_name: None,
- remote_repo: None,
- remote_branch: None,
- include: None,
- include_as: None,
- exclude: None,
- }
+ RepoFile::default()
}
}
-const RFVN_REMOTE_BRANCH: &'static str = "remote_branch";
-const RFVN_REPO_NAME: &'static str = "repo_name";
-const RFVN_REMOTE_REPO: &'static str = "remote_repo";
-const RFVN_INCLUDE_AS: &'static str = "include_as";
-const RFVN_INCLUDE: &'static str = "include";
-const RFVN_EXCLUDE: &'static str = "exclude";
-
-#[derive(Clone, PartialEq)]
-pub enum RepoFileVariableName {
- VarRemoteRepo,
- VarRepoName,
- VarRemoteBranch,
- VarIncludeAs,
- VarExclude,
- VarInclude,
- VarUnknown,
-}
-use RepoFileVariableName::*;
-impl From for &'static str {
- fn from(original: RepoFileVariableName) -> &'static str {
- match original {
- VarRepoName => RFVN_REPO_NAME,
- VarRemoteRepo => RFVN_REMOTE_REPO,
- VarRemoteBranch => RFVN_REMOTE_BRANCH,
- VarIncludeAs => RFVN_INCLUDE_AS,
- VarInclude => RFVN_INCLUDE,
- VarExclude => RFVN_EXCLUDE,
- VarUnknown => "",
- }
- }
-}
-impl From for RepoFileVariableName {
- fn from(value: String) -> RepoFileVariableName {
- match value.as_str() {
- RFVN_REMOTE_BRANCH => VarRemoteBranch,
- RFVN_REPO_NAME => VarRepoName,
- RFVN_INCLUDE => VarInclude,
- RFVN_EXCLUDE => VarExclude,
- RFVN_REMOTE_REPO => VarRemoteRepo,
- RFVN_INCLUDE_AS => VarIncludeAs,
- _ => VarUnknown,
- }
+pub fn read_file_into_lines(filename: &str) -> Vec {
+ let repo_file_path = Path::new(filename);
+ if !repo_file_path.exists() {
+ die!("Failed to find repo_file: {}", filename);
}
-}
+ let file = File::open(repo_file_path);
+ if let Err(file_error) = file {
+ die!("Failed to open file: {}, {}", filename, file_error);
+ }
-#[derive(PartialEq)]
-enum RepoFileVariableType {
- TypeString,
- TypeArray,
- TypeUnknown,
+ let file_contents = file.unwrap();
+ let reader = BufReader::new(file_contents);
+ reader.lines().map(|x| x.unwrap()).collect()
}
-use RepoFileVariableType::*;
-struct RepoFileVariable {
- name: RepoFileVariableName,
- value: Vec,
- complete: bool,
- var_type: RepoFileVariableType,
+pub fn line_is_break(line: &String) -> bool {
+ for c in line.chars() {
+ if c != ' ' {
+ return false;
+ }
+ }
+ true
}
-fn get_variable_name(text: &String) -> String {
- let equals_index = text.find("=").unwrap();
- let str_before_equals: String = text.chars().take(equals_index).collect();
- return str_before_equals.replace(" ", "");
+pub fn parse_repo_file_from_toml(filename: &str) -> RepoFile {
+ let lines = read_file_into_lines(filename);
+ parse_repo_file_from_toml_lines(lines)
}
-// a variable is being assigned. it is expected
-// that the next character after the equals is either
-// a quote, or an opening parentheses
-fn char_after_equals(text: &String) -> char {
- let equals_index = text.find("=").unwrap();
- for c in text.chars().skip(equals_index + 1) {
- if !c.is_whitespace() {
- return c;
+pub fn parse_repo_file_from_toml_lines(lines: Vec) -> RepoFile {
+ // even though this is a toml file, and we have a toml parser
+ // we still want to split by lines, and then parse specific sections
+ // this is because if a user has:
+ // [include]
+ // this = that
+ //
+ //
+ // exclude = [ something ]
+ // then toml will parse the exclude array as if its part of the include table
+ // so we split the file into sections separated by 2 'break' lines
+ // a break line is a line that only contains whitespace or a comment
+ let mut last_line_was_break = false;
+ let mut segment_indices = vec![];
+ for (line_ind, line) in lines.iter().enumerate() {
+ if line_is_break(line) {
+ if last_line_was_break {
+ segment_indices.push(line_ind);
+ last_line_was_break = false;
+ } else {
+ last_line_was_break = true;
+ }
}
}
- return ' ';
-}
-
-fn get_all_strings(text: &String) -> Option> {
- let mut strings: Vec = vec![];
- let mut current_string = String::from("");
- let mut should_append_string = false;
- for c in text.chars() {
- if c == '#' {
- break;
+ // always add a segment break to the end of the file
+ segment_indices.push(lines.len());
+
+ // group back the lines that are part of a contiguous segment
+ let mut current_index = 0;
+ let mut toml_segments = vec![];
+ for i in segment_indices {
+ let string_vec: Vec<&String> = lines.iter().skip(current_index).take(i - current_index).collect();
+ if string_vec.len() == 0 { continue; }
+
+ // we can calculate exactly how big the toml_segment is. its the sum
+ // of all the lengths of each string in string_vec plus a few
+ // newlines in between each string.
+ let mut string_size = string_vec.iter().map(|s| s.len()).sum();
+ // here we add the number of newlines there will be
+ string_size += string_vec.len() - 1;
+
+ let mut toml_segment = String::with_capacity(string_size);
+ toml_segment.push_str(string_vec[0]);
+ for j in 1..string_vec.len() {
+ toml_segment.push('\n');
+ toml_segment.push_str(string_vec[j]);
}
- // found the start of a string
- if c == '"' && current_string == "" {
- should_append_string = true;
- }
- if c != '"' && should_append_string {
- current_string.push(c);
- }
- if c == '"' && current_string != "" {
- strings.push(current_string.clone());
- current_string = String::from("");
- should_append_string = false;
- }
- }
- // when we exit loop, current string should be empty
- // if its not, then it was a failure to parse and we should
- // return empty vector
- if current_string != "" {
- return None;
+ toml_segments.push(toml_segment);
+ current_index = i;
}
- return Some(strings);
+ parse_repo_file_from_toml_segments(toml_segments)
}
-fn is_end_of_array(text: &String) -> bool {
- let mut is_end = false;
- for c in text.chars() {
- if c == '#' {
- break;
- }
- if c == ')' {
- is_end = true;
- break;
- }
+pub fn toml_value_to_string_opt(toml_value: &Value) -> Option {
+ match toml_value.as_str() {
+ Some(s) => Some(s.to_owned()),
+ None => None,
}
- return is_end;
}
-fn parse_variable(variable: &mut RepoFileVariable, text: &String, line_num: usize) {
- if variable.name == VarUnknown && text.contains("=") {
- // variable is empty, and this line
- // contains an equal sign, so we assume the variable
- // is being created
- variable.name = get_variable_name(&text).into();
- variable.var_type = match char_after_equals(&text) {
- '(' => TypeArray,
- '"' => TypeString,
- _ => TypeUnknown,
- };
+pub fn parse_repo_section(toml_value: &Value, repofile: &mut RepoFile) {
+ if let Value::Table(ref t) = toml_value {
+ for (k, v) in t {
+ match k.as_str() {
+ "remote" => repofile.remote_repo = toml_value_to_string_opt(v),
+ "name" => repofile.repo_name = toml_value_to_string_opt(v),
+ "branch" => repofile.remote_branch = toml_value_to_string_opt(v),
+ _ => (),
+ }
+ }
}
+}
- if variable.name == VarUnknown {
- die!("Invalid variable name found on line {}:\n\"{}\"", line_num, text);
+pub fn parse_include_as_section(toml_value: &Value, repofile: &mut RepoFile) {
+ if let Value::Table(ref t) = toml_value {
+ let mut include_as = vec![];
+ for (k, v) in t {
+ if let Some(s) = v.as_str() {
+ include_as.push(k.to_owned());
+ include_as.push(s.to_string());
+ }
+ }
+ repofile.include_as = Some(include_as);
}
+}
- if variable.var_type == TypeUnknown {
- die!("Failed to parse line {}:\n\"{}\"", line_num, text);
+pub fn toml_value_to_vec(toml_value: &Value) -> Vec {
+ let mut toml_vec = vec![];
+ if let Value::Array(ref a) = toml_value {
+ for v in a {
+ if let Some(s) = v.as_str() {
+ toml_vec.push(s.to_owned());
+ }
+ }
+ } else if let Value::String(s) = toml_value {
+ toml_vec.push(s.to_owned());
}
+ toml_vec
+}
- let strings = get_all_strings(&text);
- if let None = strings {
- die!("Failed to parse variable at line {}:\n\"{}\"", line_num, text);
+pub fn parse_include_section(toml_value: &Value, repofile: &mut RepoFile) {
+ let toml_vec = toml_value_to_vec(toml_value);
+ if toml_vec.len() > 0 {
+ repofile.include = Some(toml_vec);
}
+}
- match variable.var_type {
- // easiest case to parse. just get everything between the quotes
- // there should only be one string
- TypeString => {
- variable.value = vec![strings.unwrap()[0].clone()];
- variable.complete = true;
- },
- // harder to parse. add all the strings we found
- // and then only mark it complete if we also found the
- // end of the array
- TypeArray => {
- variable.value.append(&mut strings.unwrap());
- variable.complete = is_end_of_array(&text);
- },
- // we already checked for TypeUnknown above
- _ => (),
+pub fn parse_exclude_section(toml_value: &Value, repofile: &mut RepoFile) {
+ let toml_vec = toml_value_to_vec(toml_value);
+ if toml_vec.len() > 0 {
+ repofile.exclude = Some(toml_vec);
}
}
-fn add_variable_to_repo_file(repofile: &mut RepoFile, variable: &mut RepoFileVariable) {
- match variable.name {
- VarRemoteRepo => repofile.remote_repo = Some(variable.value[0].clone()),
- VarIncludeAs => repofile.include_as = Some(variable.value.clone()),
- VarExclude => repofile.exclude = Some(variable.value.clone()),
- VarInclude => repofile.include = Some(variable.value.clone()),
- VarRepoName => repofile.repo_name = Some(variable.value[0].clone()),
- VarRemoteBranch => repofile.remote_branch = Some(variable.value[0].clone()),
- _ => (),
+
+pub fn parse_repo_file_from_toml_segments(
+ toml_segments: Vec
+) -> RepoFile {
+ let mut repo_file = RepoFile::default();
+ // now we have toml_segments where each segment can be its own toml file
+ // we parse each into a toml::Value, and then apply the result into a repo file object
+ for s in toml_segments {
+ let tomlvalue = s.parse::();
+ if tomlvalue.is_err() { continue; } // TODO: report parse error to user
+ let tomlvalue = tomlvalue.unwrap();
+
+ if let Value::Table(ref t) = tomlvalue {
+ for (k, v) in t {
+ match k.as_str() {
+ "repo" => parse_repo_section(v, &mut repo_file),
+ "include_as" => parse_include_as_section(v, &mut repo_file),
+ "include" => parse_include_section(v, &mut repo_file),
+ "exclude" => parse_exclude_section(v, &mut repo_file),
+ _ => (),
+ }
+ }
+ }
}
- variable.name = VarUnknown;
- variable.value = vec![];
+ repo_file
}
-// returns true if line is not a full line comment
-// and if line is not entirely whitespace
-fn should_parse_line(text: &String) -> bool {
- let mut is_entirely_whitespace = true;
- let mut is_full_line_comment = false;
- for c in text.chars() {
- if c.is_whitespace() {
- continue;
- } else {
- is_entirely_whitespace = false;
- }
- if c == '#' {
- is_full_line_comment = true;
+pub fn generate_repo_file_section_from_list(
+ list: &Option>,
+) -> Option {
+ match list {
+ None => None,
+ Some(ref string_vec) => {
+ // if theres only one element in vec
+ // make it a string instead of array
+ if string_vec.len() == 1 {
+ return Some(toml::Value::String(string_vec[0].clone()));
+ }
+
+ let mut toml_vec = vec![];
+ for include in string_vec {
+ toml_vec.push(toml::Value::String(include.clone()));
+ }
+ Some(toml::Value::Array(toml_vec))
}
- break;
}
-
- return !is_full_line_comment && !is_entirely_whitespace;
}
-
-pub fn parse_repo_file(filename: &str) -> RepoFile {
- let repo_file_path = Path::new(filename);
- if !repo_file_path.exists() {
- die!("Failed to find repo_file: {}", filename);
+pub fn generate_repo_file_section_include(
+ repofile: &RepoFile
+) -> Option {
+ generate_repo_file_section_from_list(&repofile.include)
+}
+pub fn generate_repo_file_section_exclude(
+ repofile: &RepoFile
+) -> Option {
+ generate_repo_file_section_from_list(&repofile.exclude)
+}
+pub fn generate_repo_file_section_repo(
+ repofile: &RepoFile
+) -> Option {
+ let mut toml_map = toml::map::Map::new();
+ let matches = [
+ ("name", &repofile.repo_name),
+ ("remote", &repofile.remote_repo),
+ ("branch", &repofile.remote_branch),
+ ];
+ for (key, repofile_variable) in matches.iter() {
+ match repofile_variable {
+ None => (),
+ Some(ref s) => {
+ toml_map.insert(key.to_string(), toml::Value::String(s.clone()));
+ },
+ }
}
- let file = File::open(repo_file_path);
- if let Err(file_error) = file {
- die!("Failed to open file: {}, {}", filename, file_error);
+ if toml_map.len() > 0 {
+ Some(toml::Value::Table(toml_map))
+ } else {
+ None
}
-
- let file_contents = file.unwrap();
- let reader = BufReader::new(file_contents);
- let lines: Vec = reader.lines().map(|x| x.unwrap()).collect();
- return parse_repo_file_from_lines(lines);
}
+pub fn generate_repo_file_section_include_as(
+ repofile: &RepoFile
+) -> Option {
+ let mut toml_map = toml::map::Map::new();
+
+ match repofile.include_as {
+ None => (),
+ Some(ref include_as_vec) => {
+ // has to be even
+ assert!(include_as_vec.len() % 2 == 0);
+ let mut i = 0;
+ while i < include_as_vec.len() {
+ let key = &include_as_vec[i];
+ let value = &include_as_vec[i + 1];
+ toml_map.insert(key.clone(), toml::Value::String(value.clone()));
+ i += 2;
+ }
+ },
+ }
-pub fn parse_repo_file_from_lines(lines: Vec) -> RepoFile {
- let mut repofile_obj = RepoFile::new();
-
- // this will be modified by the parse_variable func above
- // everytime this variable is "complete", it will be added
- // to the RepoFile struct
- let mut current_variable = RepoFileVariable{
- name: VarUnknown,
- value: vec![],
- complete: false,
- var_type: TypeUnknown,
- };
-
- for (line_num, line) in lines.iter().enumerate() {
- if should_parse_line(&line) {
- parse_variable(&mut current_variable, line, line_num);
- }
+ if toml_map.len() > 0 {
+ Some(toml::Value::Table(toml_map))
+ } else {
+ None
+ }
+}
- if current_variable.complete {
- add_variable_to_repo_file(&mut repofile_obj, &mut current_variable);
- current_variable.complete = false;
- }
+pub fn generate_repo_file_toml(
+ repofile: &RepoFile,
+) -> String {
+ // the include and exclude
+ // sections need to be done
+ // seperately because
+ // they need 2 empty lines between
+ // the other sections to be parsed correctly
+ let include_section = generate_repo_file_section_include(repofile);
+ let exclude_section = generate_repo_file_section_exclude(repofile);
+ let repo_section = generate_repo_file_section_repo(repofile);
+ let include_as_section = generate_repo_file_section_include_as(repofile);
+
+ let mut toml_map = toml::map::Map::new();
+
+ if let Some(toml_value) = repo_section {
+ toml_map.insert("repo".into(), toml_value);
+ }
+ if let Some(toml_value) = include_section {
+ toml_map.insert("include".into(), toml_value);
+ }
+ if let Some(toml_value) = exclude_section {
+ toml_map.insert("exclude".into(), toml_value);
}
- return repofile_obj;
+ if let Some(toml_value) = include_as_section {
+ toml_map.insert("include_as".into(), toml_value);
+ }
+
+ let toml_table = toml::Value::Table(toml_map);
+ toml_table.to_string()
}
#[cfg(test)]
mod test {
use super::RepoFile;
- use super::parse_repo_file_from_lines;
+ use super::parse_repo_file_from_toml_lines;
+ use super::generate_repo_file_toml;
- #[test]
- #[should_panic(expected = "Invalid variable name")]
- fn should_panic_if_finds_unknown_var() {
- let lines: Vec = vec![
- "my_unknown_var=something".into()
- ];
- parse_repo_file_from_lines(lines);
+ fn parse_from_lines(toml_str: &str) -> RepoFile {
+ let lines: Vec = toml_str.split('\n').map(|s| s.to_string()).collect();
+ parse_repo_file_from_toml_lines(lines)
}
#[test]
- #[should_panic(expected = "Failed to parse line")]
- fn should_panic_if_variable_type_unknown() {
- let lines: Vec = vec![
- "remote_repo=something".into()
- ];
- parse_repo_file_from_lines(lines);
+ fn toml_parse_repo_quotes_dont_matter() {
+ let toml_str1 = r#"
+ [repo]
+ "name" = "hello"
+ "remote" = "https://githost.com/repo"
+ "#;
+ let toml_str2 = r#"
+ [repo]
+ name = "hello"
+ remote = "https://githost.com/repo"
+ "#;
+ let repofile1 = parse_from_lines(toml_str1);
+ let repofile2 = parse_from_lines(toml_str2);
+ assert_eq!(repofile1, repofile2);
+ println!("{:#?}", repofile1);
}
#[test]
- fn should_handle_big_space_in_array() {
- let lines: Vec = vec![
- "include_as=(\"abc\" \"xyz\"".into(),
- "".into(),
- "\t\t \t".into(),
- "\n\n \n\t\t".into(),
- ")".into(),
- " ".into(),
- "exclude=\"yyy\"".into(),
- ];
- let mut expectedrepofileobj = RepoFile::new();
- expectedrepofileobj.include_as = Some(vec![
- "abc".into(), "xyz".into(),
- ]);
- expectedrepofileobj.exclude = Some(vec!["yyy".into()]);
- let repofileobj = parse_repo_file_from_lines(lines);
- assert_eq!(expectedrepofileobj, repofileobj);
+ fn toml_parse_remote_branch() {
+ let toml_str = r#"
+ [repo]
+ branch="something"
+ "#;
+ let repofile = parse_from_lines(toml_str);
+ assert_eq!(repofile.remote_branch.unwrap(), "something");
}
#[test]
- fn should_return_repo_file_obj() {
- let lines: Vec = vec![
- "remote_repo=\"something\"".into(),
- "include_as=(".into(),
- " \"one\"".into(),
- " \"two\" \"three\"".into(),
- " )".into(),
- "exclude=(\"abc\")".into(),
- " include=(\"xyz\" \"qqq\" \"www\")".into(),
- ];
+ fn toml_should_return_repo_file_obj() {
+ let toml_str = r#"
+ [repo]
+ remote = "something"
+ [include_as]
+ "one/x/y/" = "two/x/y/"
+ "three/a/b" = "four/a/b"
+
+
+ exclude = ["abc"]
+ include = ["xyz", "qqq", "www"]
+ "#;
let mut expectedrepofileobj = RepoFile::new();
expectedrepofileobj.remote_repo = Some("something".into());
expectedrepofileobj.include_as = Some(vec![
- "one".into(), "two".into(), "three".into()
+ "one/x/y/".into(), "two/x/y/".into(),
+ "three/a/b".into(), "four/a/b".into(),
]);
expectedrepofileobj.exclude = Some(vec!["abc".into()]);
expectedrepofileobj.include = Some(vec![
"xyz".into(), "qqq".into(), "www".into(),
]);
- let repofileobj = parse_repo_file_from_lines(lines);
+ let repofileobj = parse_from_lines(toml_str);
assert_eq!(expectedrepofileobj, repofileobj);
}
#[test]
- fn should_parse_repo_name() {
- let lines: Vec = vec![
- "repo_name=\"something\"".into(),
- ];
- let mut expectedrepofileobj = RepoFile::new();
- expectedrepofileobj.repo_name = Some("something".into());
- let repofileobj = parse_repo_file_from_lines(lines);
- assert_eq!(expectedrepofileobj, repofileobj);
+ fn toml_space_parse_workd() {
+ let toml_str = r#"
+ [include_as]
+ " " = "some path/lib/"
+ "something/else" = " "
+ "#;
+ let repofile = parse_from_lines(toml_str);
+ let include_as = repofile.include_as.unwrap();
+ assert_eq!(include_as.len(), 4);
+ assert_eq!(include_as[0], " ");
+ assert_eq!(include_as[1], "some path/lib/");
+ assert_eq!(include_as[2], "something/else");
+ assert_eq!(include_as[3], " ");
}
#[test]
- fn should_parse_remote_branch() {
- let lines: Vec = vec![
- "remote_branch=\"something\"".into(),
- ];
- let mut expectedrepofileobj = RepoFile::new();
- expectedrepofileobj.remote_branch = Some("something".into());
- let repofileobj = parse_repo_file_from_lines(lines);
- assert_eq!(expectedrepofileobj, repofileobj);
+ fn toml_comments_not_included() {
+ let toml_str = r#"
+ [repo]
+ name = "somename"
+ # comment1
+ # comment2
+ # comment3
+ branch = "somebranch"
+ "#;
+ let repofile = parse_from_lines(toml_str);
+ assert_eq!(repofile.repo_name.unwrap(), "somename");
+ assert_eq!(repofile.remote_branch.unwrap(), "somebranch");
+ }
+
+ #[test]
+ fn generate_repo_file_works() {
+ let mut repofile = RepoFile::default();
+ repofile.include = Some(vec!["hello".into()]);
+ repofile.exclude = Some(vec!["abc".into(), "xyz".into()]);
+ repofile.repo_name = Some("reponame".into());
+ repofile.remote_branch = Some("mybranch".into());
+ repofile.include_as = Some(vec![
+ "lib/".into(), " ".into(),
+ "something.txt".into(), "else.txt".into(),
+ ]);
+ let toml_str = generate_repo_file_toml(&repofile);
+ assert!(toml_str.contains("include = \"hello\""));
+ assert!(toml_str.contains("exclude = [\"abc\", \"xyz\"]"));
+ assert!(toml_str.contains("[repo]"));
+ assert!(toml_str.contains("name = \"reponame\""));
+ assert!(toml_str.contains("branch = \"mybranch\""));
+ assert!(toml_str.contains("[include_as]"));
+ assert!(toml_str.contains("\"lib/\" = \" \""));
+ assert!(toml_str.contains("\"something.txt\" = \"else.txt\""));
+ }
+
+ #[test]
+ fn generated_repo_file_can_be_read() {
+ let mut repofile = RepoFile::default();
+ repofile.include = Some(vec!["hello".into()]);
+ repofile.exclude = Some(vec!["abc".into(), "xyz".into()]);
+ repofile.repo_name = Some("reponame".into());
+ repofile.remote_branch = Some("mybranch".into());
+ repofile.include_as = Some(vec![
+ "lib/".into(), " ".into(),
+ "something.txt".into(), "else.txt".into(),
+ ]);
+ let toml_str = generate_repo_file_toml(&repofile);
+
+ let repofile_parsed = parse_from_lines(toml_str.as_str());
+ assert_eq!(repofile, repofile_parsed);
}
}
diff --git a/src/split.rs b/src/split.rs
index 970da7b..4e7ce16 100644
--- a/src/split.rs
+++ b/src/split.rs
@@ -12,9 +12,10 @@ use super::commands::VERBOSE_ARG;
use super::commands::REBASE_ARG;
use super::commands::TOPBASE_ARG;
use super::commands::OUTPUT_BRANCH_ARG;
+use super::commands::NUM_COMMITS_ARG;
use super::repo_file;
use super::repo_file::RepoFile;
-use super::git_helpers;
+use super::git_helpers3;
use super::exec_helpers;
use super::die;
@@ -33,12 +34,13 @@ pub struct Runner<'a> {
pub repo_root_dir: PathBuf,
pub topbase_top_ref: Option,
pub repo_original_ref: Option,
- pub repo: Option,
pub input_branch: Option,
pub output_branch: Option,
pub include_arg_str: Option>,
pub include_as_arg_str: Option>,
pub exclude_arg_str: Option>,
+ // only relevant to split-in
+ pub num_commits: Option,
pub status: i32,
}
@@ -50,6 +52,14 @@ impl<'a> Runner<'a> {
let is_topbase = matches.occurrences_of(TOPBASE_ARG[0]) > 0;
let output_branch = matches.value_of(OUTPUT_BRANCH_ARG[0]);
let repo_file_path = matches.value_of(REPO_FILE_ARG);
+ let num_commits = matches.value_of(NUM_COMMITS_ARG);
+ let num_commits = match num_commits {
+ None => None,
+ Some(s) => match s.parse::() {
+ Err(_) => None,
+ Ok(u) => Some(u),
+ },
+ };
Runner {
repo_file_path: repo_file_path,
status: 0,
@@ -63,11 +73,11 @@ impl<'a> Runner<'a> {
topbase_top_ref: None,
repo_original_ref: None,
current_dir: PathBuf::new(),
- repo: None,
repo_root_dir: PathBuf::new(),
include_arg_str: None,
include_as_arg_str: None,
exclude_arg_str: None,
+ num_commits: num_commits,
log_p: if is_dry_run { " # " } else { "" },
input_branch: None,
output_branch: if let Some(branch_name) = output_branch {
@@ -86,9 +96,9 @@ impl<'a> Runner<'a> {
// get the current ref that this git repo is pointing to
// save it for later
pub fn save_current_ref(mut self) -> Self {
- self.repo_original_ref = match self.repo {
- Some(ref repo) => git_helpers::get_current_ref(repo),
- None => None,
+ self.repo_original_ref = match git_helpers3::get_current_ref() {
+ Ok(s) => Some(s),
+ Err(_) => None,
};
self
}
@@ -100,24 +110,21 @@ impl<'a> Runner<'a> {
return self;
}
- match self.repo {
- Some(ref r) => {
- let success = git_helpers::make_orphan_branch_and_checkout(
- orphan_branch,
- r,
- ).is_ok();
- if ! success {
- die!("Failed to checkout orphan branch");
- }
- // on a new orphan branch our existing files appear in the stage
- // we need to essentially do "git rm -rf ."
- let success = git_helpers::remove_index_and_files(r).is_ok();
- if ! success {
- die!("Failed to remove git indexed files after making orphan");
- }
- },
- _ => die!("Something went horribly wrong!"),
- };
+ let success = git_helpers3::make_orphan_branch_and_checkout(
+ orphan_branch,
+ ).is_ok();
+ if ! success {
+ die!("Failed to checkout orphan branch");
+ }
+ // on a new orphan branch our existing files appear in the stage
+ // we need to do "git rm -rf ."
+ // the 'dot' should be safe to do as long as
+ // we are in the root of the repository, but this method
+ // should only be called after we cd into the root
+ let success = git_helpers3::remove_index_and_files().is_ok();
+ if ! success {
+ die!("Failed to remove git indexed files after making orphan");
+ }
if self.verbose {
println!("{}created and checked out orphan branch {}", self.log_p, orphan_branch);
}
@@ -186,29 +193,24 @@ impl<'a> Runner<'a> {
Some(new_remote_branch) => Some(new_remote_branch),
};
- match self.repo {
- None => die!("Failed to find repo?"),
- Some(ref r) => {
- match (self.dry_run, &self.input_branch) {
- (true, Some(branch_name)) => println!("git merge {}", branch_name),
- (true, None) => println!("git pull {}", remote_repo.unwrap()),
- (false, Some(branch_name)) => {
- println!("{}Merging {}", self.log_p, branch_name);
- git_helpers::merge_branches(&r, &branch_name[..], None);
- },
- (false, None) => {
- let remote_repo_name = remote_repo.clone().unwrap_or("?".into());
- let remote_branch_name = remote_branch.clone().unwrap_or("".into());
- let remote_string = if remote_branch_name != "" {
- format!("{}:{}", remote_repo_name, remote_branch_name)
- } else { format!("{}", remote_repo_name) };
- println!("{}Pulling from {}", self.log_p, remote_string);
- let res = git_helpers::pull(&r, &remote_repo.unwrap()[..], remote_branch);
- if res.is_err() {
- die!("Failed to pull remote repo {}", remote_string);
- }
- },
- };
+ match (self.dry_run, &self.input_branch) {
+ (true, Some(branch_name)) => println!("git merge {}", branch_name),
+ (true, None) => println!("git pull {}", remote_repo.unwrap()),
+ (false, Some(branch_name)) => {
+ println!("{}Merging {}", self.log_p, branch_name);
+ git_helpers3::merge_branch(&branch_name[..]);
+ },
+ (false, None) => {
+ let remote_repo_name = remote_repo.clone().unwrap_or("?".into());
+ let remote_branch_name = remote_branch.clone().unwrap_or("".into());
+ let remote_string = if remote_branch_name != "" {
+ format!("{}:{}", remote_repo_name, remote_branch_name)
+ } else { format!("{}", remote_repo_name) };
+ println!("{}Pulling from {}", self.log_p, remote_string);
+ let res = git_helpers3::pull(&remote_repo.unwrap()[..], remote_branch, self.num_commits);
+ if res.is_err() {
+ die!("Failed to pull remote repo {}", remote_string);
+ }
},
};
self
@@ -262,7 +264,7 @@ impl<'a> Runner<'a> {
pub fn get_repo_file(mut self) -> Self {
// safe to unwrap because its required
let repo_file_name = self.repo_file_path.unwrap();
- self.repo_file = repo_file::parse_repo_file(repo_file_name);
+ self.repo_file = repo_file::parse_repo_file_from_toml(repo_file_name);
if self.verbose {
println!("{}got repo file: {}", self.log_p, repo_file_name);
}
@@ -281,9 +283,12 @@ impl<'a> Runner<'a> {
self
}
pub fn get_repository_from_current_dir(mut self) -> Self {
- let (repo, repo_path) = git_helpers::get_repository_and_root_directory(&self.current_dir);
- self.repo = Some(repo);
- self.repo_root_dir = repo_path;
+ let repo_path = match git_helpers3::get_repo_root() {
+ Ok(p) => p,
+ Err(_) => die!("Must run this command from a git repository"),
+ };
+
+ self.repo_root_dir = PathBuf::from(repo_path);
if self.verbose {
println!("{}found repo path: {}", self.log_p, self.repo_root_dir.display());
}
diff --git a/src/split_in.rs b/src/split_in.rs
index 882df9e..24efc79 100644
--- a/src/split_in.rs
+++ b/src/split_in.rs
@@ -3,13 +3,15 @@ use clap::ArgMatches;
use super::commands::INPUT_BRANCH_ARG;
use super::commands::AS_SUBDIR_ARG;
use super::commands::REPO_URI_ARG;
+use super::commands::GEN_REPO_FILE_ARG;
use super::split::panic_if_array_invalid;
use super::split::Runner;
use super::split::has_both_topbase_and_rebase;
-use super::git_helpers;
+use super::git_helpers3;
use super::exec_helpers;
use super::split::try_get_repo_name_from_remote_repo;
use super::repo_file::RepoFile;
+use super::repo_file::generate_repo_file_toml;
use super::die;
use std::convert::From;
use std::fs;
@@ -28,15 +30,10 @@ impl<'a> SplitIn for Runner<'a> {
self.input_branch = match self.matches.value_of(INPUT_BRANCH_ARG) {
None => None,
Some(branch_name) => {
- match &self.repo {
- None => die!("Failed to find repo for some reason"),
- Some(ref repo) => {
- if ! git_helpers::branch_exists(branch_name, repo) {
- die!("You specified an input branch of {}, but that branch was not found", branch_name);
- }
- Some(branch_name.into())
- },
+ if ! git_helpers3::branch_exists(branch_name) {
+ die!("You specified an input branch of {}, but that branch was not found", branch_name);
}
+ Some(branch_name.into())
},
};
@@ -439,7 +436,16 @@ pub fn run_split_in_as(matches: &ArgMatches) {
}
match runner.status {
- 0 => println!("{}Success!", log_p),
+ 0 => {
+ if matches.occurrences_of(GEN_REPO_FILE_ARG[0]) > 0 {
+ let repo_file_name = runner.output_branch.unwrap_or("meta".into());
+ match generate_repo_file(&repo_file_name, &runner.repo_file) {
+ Err(e) => println!("Failed to generate repo file: {}", e),
+ Ok(_) => (),
+ }
+ }
+ println!("{}Success!", log_p)
+ },
c => {
std::process::exit(c);
},
@@ -447,6 +453,24 @@ pub fn run_split_in_as(matches: &ArgMatches) {
}
+pub fn generate_repo_file(
+ repo_name: &str,
+ repofile: &RepoFile
+) -> Result<(), String> {
+ let repo_file_path_str = format!("{}.rf", repo_name);
+ let repo_file_path = std::path::PathBuf::from(&repo_file_path_str);
+ if repo_file_path.exists() {
+ let err_str = format!("{}.rf already exists", repo_name);
+ return Err(err_str);
+ }
+
+ let repo_file_str = generate_repo_file_toml(repofile);
+ match std::fs::write(repo_file_path_str, repo_file_str) {
+ Ok(_) => Ok(()),
+ Err(e) => Err(e.to_string()),
+ }
+}
+
#[cfg(test)]
mod test {
use super::*;
diff --git a/src/split_out.rs b/src/split_out.rs
index 2040493..8f2cbfa 100644
--- a/src/split_out.rs
+++ b/src/split_out.rs
@@ -5,7 +5,7 @@ use super::split::Runner;
use super::split::try_get_repo_name_from_remote_repo;
use super::split::has_both_topbase_and_rebase;
use super::repo_file::RepoFile;
-use super::git_helpers;
+use super::git_helpers3;
use super::commands::AS_SUBDIR_ARG;
use super::commands::OUTPUT_BRANCH_ARG;
use super::die;
@@ -79,22 +79,14 @@ impl<'a> SplitOut for Runner<'a> {
println!("git checkout {}", output_branch_name);
return self;
}
- let output_branch_ref = format!("refs/heads/{}", output_branch_name);
-
- match self.repo {
- Some(ref r) => {
- match git_helpers::checkout_to_branch_and_clear_index(
- output_branch_ref.as_str(),
- r,
- ) {
- Ok(_) => (),
- Err(e) => {
- die!("Failed to checkout branch {}", e);
- }
- };
- },
- _ => die!("Something went horribly wrong!"),
- };
+
+ if let Err(e) = git_helpers3::checkout_branch(
+ output_branch_name.as_str(),
+ false,
+ ) {
+ die!("Failed to checkout branch {}", e);
+ }
+
if self.verbose {
println!("{} checked out branch {}", self.log_p, output_branch_name);
}
@@ -109,18 +101,14 @@ impl<'a> SplitOut for Runner<'a> {
return self;
}
- match self.repo {
- Some(ref r) => {
- let success = git_helpers::make_new_branch_from_head_and_checkout(
- r,
- output_branch_name.as_str(),
- ).is_ok();
- if ! success {
- die!("Failed to checkout new branch");
- }
- },
- _ => die!("Something went horribly wrong!"),
- };
+ let success = git_helpers3::checkout_branch(
+ output_branch_name.as_str(),
+ true,
+ ).is_ok();
+ if ! success {
+ die!("Failed to checkout new branch");
+ }
+
if self.verbose {
println!("{}created and checked out new branch {}", self.log_p, output_branch_name);
}
@@ -129,14 +117,8 @@ impl<'a> SplitOut for Runner<'a> {
}
fn delete_branch(self, branch_name: &str) -> Self {
- match self.repo {
- Some(ref r) => {
- match git_helpers::delete_branch(branch_name, r) {
- Err(e) => println!("Failed to delete branch: {}. {}", branch_name, e),
- Ok(_) => (),
- }
- },
- None => println!("Failed to delete branch: {}", branch_name),
+ if let Err(e) = git_helpers3::delete_branch(branch_name) {
+ println!("Failed to delete branch: {}. {}", branch_name, e);
}
self
}
diff --git a/src/topbase.rs b/src/topbase.rs
index 9f72df5..3b3a94d 100644
--- a/src/topbase.rs
+++ b/src/topbase.rs
@@ -1,7 +1,9 @@
use clap::ArgMatches;
use std::collections::HashSet;
-use super::git_helpers;
+use super::git_helpers3;
+use super::git_helpers3::Commit;
+use super::git_helpers3::Oid;
use super::exec_helpers;
use super::split::Runner;
use super::check::topbase_check_alg;
@@ -17,20 +19,15 @@ pub trait Topbase {
impl<'a> Topbase for Runner<'a> {
fn topbase(mut self) -> Self {
- let repo = match self.repo {
- Some(ref r) => r,
- None => die!("failed to get repo?"),
- };
-
// for split commands, we always use current ref,
// but for topbase command, we check if user provided a top branch
// if user provided one, we use that, otherwise we use current
let current_branch = if let Some(ref b) = self.topbase_top_ref {
b.clone()
} else {
- match git_helpers::get_current_ref(repo) {
- Some(s) => s,
- None => {
+ match git_helpers3::get_current_ref() {
+ Ok(s) => s,
+ Err(_) => {
println!("Failed to get current branch. not going to rebase");
return self;
},
@@ -47,7 +44,7 @@ impl<'a> Topbase for Runner<'a> {
};
let all_upstream_blobs = get_all_blobs_in_branch(upstream_branch.as_str());
- let all_commits_of_current = match git_helpers::get_all_commits_from_ref(repo, current_branch.as_str()) {
+ let all_commits_of_current = match git_helpers3::get_all_commits_from_ref(current_branch.as_str()) {
Ok(v) => v,
Err(e) => die!("Failed to get all commits! {}", e),
};
@@ -55,9 +52,9 @@ impl<'a> Topbase for Runner<'a> {
let num_commits_of_current = all_commits_of_current.len();
let mut num_commits_to_take = 0;
let mut rebase_data = vec![];
- let mut cb = |c: &git2::Commit| {
+ let mut cb = |c: &Commit| {
num_commits_to_take += 1;
- let rebase_interactive_entry = format!("pick {} {}\n", c.id(), c.summary().unwrap());
+ let rebase_interactive_entry = format!("pick {} {}\n", c.id.long(), c.summary);
rebase_data.push(rebase_interactive_entry);
};
topbase_check_alg(all_commits_of_current, all_upstream_blobs, &mut cb);
@@ -71,56 +68,47 @@ impl<'a> Topbase for Runner<'a> {
let current_branch = current_branch.replace("refs/heads/", "");
let upstream_branch = upstream_branch.replace("refs/heads/", "");
- // log the special cases
+ // if nothing to take, dont topbase
+ // instead go back to upstream, and then
+ // delete delete the current branch
if num_commits_to_take == 0 {
- // if we have found that the most recent commit of current_branch already exists
- // on the upstream branch, we should just rebase normally (so that the branch can be fast-forwardable)
- // instead of rebasing interactively
- println!("{}most recent commit of {} exists in {}. rebasing non-interactively", self.log_p, current_branch, upstream_branch);
- } else if num_commits_to_take == num_commits_of_current {
+ if self.dry_run {
+ println!("{}Nothing to topbase. Returning to {}", self.log_p, upstream_branch);
+ println!("{}Deleting {}", self.log_p, current_branch);
+ return self;
+ }
+
+ println!("Nothing to topbase. Returning to {}", upstream_branch);
+ match git_helpers3::checkout_branch(upstream_branch.as_str(), false) {
+ Err(e) => die!("Failed to checkout back to upstream branch: {}", e),
+ _ => (),
+ }
+ println!("Deleting {}", current_branch);
+ match git_helpers3::delete_branch(current_branch.as_str()) {
+ Err(e) => die!("Failed to delete temporary branch {}: {}", current_branch, e),
+ _ => (),
+ }
+
+ return self;
+ }
+
+ // if we need to topbase the entirety of the current branch
+ // it will be better to do a regular rebase
+ let args = if num_commits_to_take == num_commits_of_current {
// if we are trying to topbase on a branch that hasnt been rebased yet,
// we dont need to topbase, and instead we need to do a regular rebase
println!("{}no commit of {} exists in {}. rebasing non-interactively", self.log_p, current_branch, upstream_branch);
- }
- let args = match num_commits_to_take {
- // if there's nothing to topbase, then we want to just
- // rebase the last commit onto the upstream branch.
- // this will allow our current branch to be fast-forwardable
- // onto upstream (well really its going to be the exact same branch)
- 0 => {
- // if current branch only has one commit, dont use the ~1
- // git rebase syntax. it will cause git rebase to fail
- let rebase_last_one = match num_commits_of_current > 1 {
- true => "~1",
- false => "",
- };
- let last_commit_arg = format!("{}{}", current_branch, rebase_last_one);
- let args = vec![
- "git".into(), "rebase".into(), "--onto".into(),
- upstream_branch.clone(),
- last_commit_arg,
- current_branch.clone()
- ];
- args
- },
- // if we need to topbase the entirety of the current branch
- // it will be better to do a regular rebase
- n => {
- if n == num_commits_of_current {
- let args = vec![
- "git".into(), "rebase".into(), upstream_branch.clone(),
- ];
- args
- } else {
- vec![]
- }
- },
+ let args = vec![
+ "git".into(), "rebase".into(), upstream_branch.clone(),
+ ];
+ args
+ } else {
+ vec![]
};
// args will have non-zero length only if
- // - we need to topbase all commits
- // - we found no commits to topbase
+ // we need to topbase all commits
if args.len() != 0 {
if self.dry_run {
let arg_str = args.join(" ");
diff --git a/test/check/end-to-end.bats b/test/check/end-to-end.bats
index fb7b6a9..7683361 100644
--- a/test/check/end-to-end.bats
+++ b/test/check/end-to-end.bats
@@ -16,7 +16,7 @@ function set_seperator() {
# I wanna use these tests for both windows (git bash)
# and linux, so I need to change the separator
if [[ -d /c/ ]]; then
- SEP="\\"
+ SEP="\\\\"
else
SEP="/"
fi
@@ -84,7 +84,10 @@ function setup() {
cd "$curr_dir"
repo_file_contents="
- remote_repo=\"..$SEP$test_remote_repo2\"
+ [repo]
+ remote = \"..$SEP$test_remote_repo2\"
+
+
include=\"abc.txt\"
"
echo "$repo_file_contents" > repo_file.sh
@@ -109,12 +112,18 @@ function setup() {
mkdir -p repo_file_dir
repo_file_contents="
- remote_repo=\"..$SEP$test_remote_repo2\"
+ [repo]
+ remote = \"..$SEP$test_remote_repo2\"
+
+
include=\"abc.txt\"
"
echo "$repo_file_contents" > repo_file_dir/repo_file1.rf
repo_file_contents="
- remote_repo=\"..$SEP$test_remote_repo2\"
+ [repo]
+ remote = \"..$SEP$test_remote_repo2\"
+
+
include=\"xyz.txt\"
"
echo "$repo_file_contents" > repo_file_dir/repo_file2.rf
@@ -145,12 +154,18 @@ function setup() {
mkdir -p repo_file_dir
repo_file_contents="
- remote_repo=\"..$SEP$test_remote_repo2\"
+ [repo]
+ remote = \"..$SEP$test_remote_repo2\"
+
+
include=\"abc.txt\"
"
echo "$repo_file_contents" > repo_file_dir/repo_file1.txt
repo_file_contents="
- remote_repo=\"..$SEP$test_remote_repo2\"
+ [repo]
+ remote = \"..$SEP$test_remote_repo2\"
+
+
include=\"xyz.txt\"
"
echo "$repo_file_contents" > repo_file_dir/repo_file2.txt
@@ -178,17 +193,26 @@ function setup() {
mkdir -p repo_file_dir
mkdir -p repo_file_dir/recurse
repo_file_contents="
- remote_repo=\"..$SEP$test_remote_repo2\"
+ [repo]
+ remote = \"..$SEP$test_remote_repo2\"
+
+
include=\"abc.txt\"
"
echo "$repo_file_contents" > repo_file_dir/repo_file1.rf
repo_file_contents="
- remote_repo=\"..$SEP$test_remote_repo2\"
+ [repo]
+ remote = \"..$SEP$test_remote_repo2\"
+
+
include=\"xyz.txt\"
"
echo "$repo_file_contents" > repo_file_dir/repo_file2.rf
repo_file_contents="
- remote_repo=\"..$SEP$test_remote_repo2\"
+ [repo]
+ remote = \"..$SEP$test_remote_repo2\"
+
+
include=\"qqq.txt\"
"
echo "$repo_file_contents" > repo_file_dir/recurse/repo_file3.rf
@@ -223,8 +247,11 @@ function setup() {
cd "$curr_dir"
repo_file_contents="
- remote_repo=\"..$SEP$test_remote_repo2\"
- include=(\"abc.txt\" \"xyz.txt\")
+ [repo]
+ remote = \"..$SEP$test_remote_repo2\"
+
+
+ include = [\"abc.txt\", \"xyz.txt\"]
"
echo "$repo_file_contents" > repo_file.sh
# simulate a point that is 'even' with the remote
@@ -253,7 +280,10 @@ function setup() {
# the fact that remote has xyz.txt should be irrelevant
# to this check
repo_file_contents="
- remote_repo=\"..$SEP$test_remote_repo2\"
+ [repo]
+ remote = \"..$SEP$test_remote_repo2\"
+
+
include=\"abc.txt\"
"
echo "$repo_file_contents" > repo_file.sh
@@ -283,10 +313,10 @@ function setup() {
# the fact that remote has xyz.txt should be irrelevant
# to this check
repo_file_contents="
- remote_repo=\"..$SEP$test_remote_repo2\"
- include_as=(
- \"abc.txt\" \"abcd.txt\"
- )
+ [repo]
+ remote = \"..$SEP$test_remote_repo2\"
+ [include_as]
+ \"abc.txt\" = \"abcd.txt\"
"
echo "$repo_file_contents" > repo_file.sh
# simulate a point that is 'even' with the remote
@@ -315,8 +345,11 @@ function setup() {
# the fact that remote has xyz.txt should be irrelevant
# to this check
repo_file_contents="
- remote_repo=\"..$SEP$test_remote_repo2\"
- exclude=(\"xy z.txt\" \"abcd.txt\")
+ [repo]
+ remote = \"..$SEP$test_remote_repo2\"
+
+
+ exclude=[\"xy z.txt\", \"abcd.txt\"]
"
echo "$repo_file_contents" > repo_file.sh
echo "abc" > abc.txt && git add abc.txt && git commit -m "abc"
@@ -346,9 +379,12 @@ function setup() {
# the fact that remote has xyz.txt should be irrelevant
# to this check
repo_file_contents="
- remote_repo=\"..$SEP$test_remote_repo2\"
- include=(\"some path/\")
- exclude=(\"some path/lib/\")
+ [repo]
+ remote = \"..$SEP$test_remote_repo2\"
+
+
+ include = \"some path/\"
+ exclude = \"some path/lib/\"
"
echo "$repo_file_contents" > repo_file.sh
echo "abc" > abc.txt && git add abc.txt && git commit -m "abc"
@@ -379,10 +415,10 @@ function setup() {
# the fact that remote has xyz.txt should be irrelevant
# to this check
repo_file_contents="
- remote_repo=\"..$SEP$test_remote_repo2\"
- include_as=(
- \" \" \"some path/lib/\"
- )
+ [repo]
+ remote = \"..$SEP$test_remote_repo2\"
+ [include_as]
+ \" \" = \"some path/lib/\"
"
echo "$repo_file_contents" > repo_file.sh
echo "abc" > abc.txt && git add abc.txt && git commit -m "abc"
@@ -413,10 +449,12 @@ function setup() {
# the fact that remote has xyz.txt should be irrelevant
# to this check
repo_file_contents="
- remote_repo=\"..$SEP$test_remote_repo2\"
- include_as=(
- \" \" \"some path/lib/\"
- )
+ [repo]
+ remote = \"..$SEP$test_remote_repo2\"
+ [include_as]
+ \" \" = \"some path/lib/\"
+
+
exclude=\"some path/lib/xy z.txt\"
"
echo "$repo_file_contents" > repo_file.sh
@@ -446,10 +484,10 @@ function setup() {
# the fact that remote has xyz.txt should be irrelevant
# to this check
repo_file_contents="
- remote_repo=\"..$SEP$test_remote_repo2\"
- include_as=(
- \" \" \"some path/lib/\"
- )
+ [repo]
+ remote = \"..$SEP$test_remote_repo2\"
+ [include_as]
+ \" \" = \"some path/lib/\"
"
echo "$repo_file_contents" > repo_file.sh
echo "abc" > abc.txt && git add abc.txt && git commit -m "abc"
@@ -480,11 +518,13 @@ function setup() {
# the fact that remote has xyz.txt should be irrelevant
# to this check
repo_file_contents="
- remote_repo=\"..$SEP$test_remote_repo2\"
- include_as=(
- \" \" \"some path/lib/\"
- )
- exclude=\"xy z.txt\"
+ [repo]
+ remote = \"..$SEP$test_remote_repo2\"
+ [include_as]
+ \" \" = \"some path/lib/\"
+
+
+ exclude = \"xy z.txt\"
"
echo "$repo_file_contents" > repo_file.sh
echo "abc" > abc.txt && git add abc.txt && git commit -m "abc"
diff --git a/test/check/usage.bats b/test/check/usage.bats
index ad5cd3e..2032722 100644
--- a/test/check/usage.bats
+++ b/test/check/usage.bats
@@ -16,7 +16,7 @@ function set_seperator() {
# I wanna use these tests for both windows (git bash)
# and linux, so I need to change the separator
if [[ -d /c/ ]]; then
- SEP="\\"
+ SEP="\\\\"
else
SEP="/"
fi
@@ -61,8 +61,9 @@ function setup() {
@test 'can optionally specify a remote branch to override repo file' {
# by default it should be whatever is in the repo file branch:
repo_file_contents="
- remote_repo=\"..$SEP$test_remote_repo2\"
- remote_branch=\"somebranch\"
+ [repo]
+ remote = \"..$SEP$test_remote_repo2\"
+ branch = \"somebranch\"
"
echo "$repo_file_contents" > repo_file.sh
@@ -70,18 +71,21 @@ function setup() {
run $PROGRAM_PATH check repo_file.sh
echo "$output"
[[ "$output" == *"Upstream: HEAD"* ]]
- [[ "$output" == *"Current: ..$SEP$test_remote_repo2 somebranch"* ]]
+ [[ "$output" == *"Current: "* ]]
+ [[ "$output" == *"$test_remote_repo2 somebranch"* ]]
# if we specify --remote otherbranch, it should override the default
run $PROGRAM_PATH check repo_file.sh --remote -b other
echo "$output"
[[ "$output" == *"Upstream: HEAD"* ]]
- [[ "$output" == *"Current: ..$SEP$test_remote_repo2 other"* ]]
+ [[ "$output" == *"Current: "* ]]
+ [[ "$output" == *"$test_remote_repo2 other"* ]]
}
@test 'can optionally specify a local branch to check from/to' {
repo_file_contents="
- remote_repo=\"..$SEP$test_remote_repo2\"
+ [repo]
+ remote = \"..$SEP$test_remote_repo2\"
"
echo "$repo_file_contents" > repo_file.sh
@@ -89,19 +93,22 @@ function setup() {
run $PROGRAM_PATH check repo_file.sh --local
echo "$output"
[[ "$output" == *"Current: HEAD"* ]]
- [[ "$output" == *"Upstream: ..$SEP$test_remote_repo2"* ]]
+ [[ "$output" == *"Upstream: "* ]]
+ [[ "$output" == *"$test_remote_repo2"* ]]
run $PROGRAM_PATH check repo_file.sh --local --local-branch other
echo "$output"
[[ "$output" == *"Current: other"* ]]
- [[ "$output" == *"Upstream: ..$SEP$test_remote_repo2"* ]]
+ [[ "$output" == *"Upstream: "* ]]
+ [[ "$output" == *"$test_remote_repo2"* ]]
}
@test 'uses remote_repo:HEAD by default' {
# by default it should be whatever is in the repo file branch:
repo_file_contents="
- remote_repo=\"..$SEP$test_remote_repo2\"
+ [repo]
+ remote = \"..$SEP$test_remote_repo2\"
"
echo "$repo_file_contents" > repo_file.sh
@@ -109,5 +116,6 @@ function setup() {
run $PROGRAM_PATH check repo_file.sh
echo "$output"
[[ "$output" == *"Upstream: HEAD"* ]]
- [[ "$output" == *"Current: ..$SEP$test_remote_repo2"* ]]
+ [[ "$output" == *"Current: "* ]]
+ [[ "$output" == *"$test_remote_repo2"* ]]
}
diff --git a/test/general/end-to-end.bats b/test/general/end-to-end.bats
index 3f74443..1f82106 100644
--- a/test/general/end-to-end.bats
+++ b/test/general/end-to-end.bats
@@ -16,7 +16,7 @@ function set_seperator() {
# I wanna use these tests for both windows (git bash)
# and linux, so I need to change the separator
if [[ -d /c/ ]]; then
- SEP="\\"
+ SEP="\\\\"
else
SEP="/"
fi
@@ -51,10 +51,10 @@ function teardown() {
@test 'can use same repo_file for split-in and split-out' {
repo_file_contents="
- remote_repo=\"..$SEP$test_remote_repo2\"
- include_as=(
- \"this/path/will/be/created/\" \" \"
- )
+ [repo]
+ remote = \"..$SEP$test_remote_repo2\"
+ [include_as]
+ \"this/path/will/be/created/\" = \" \"
"
echo "$repo_file_contents" > repo_file.sh
@@ -87,10 +87,10 @@ function teardown() {
@test 'split-in by default should fail if the output branch it wants to create already exists' {
repo_file_contents="
- remote_repo=\"..$SEP$test_remote_repo2\"
- include_as=(
- \"this/path/will/be/created/\" \" \"
- )
+ [repo]
+ remote = \"..$SEP$test_remote_repo2\"
+ [include_as]
+ \"this/path/will/be/created/\" = \" \"
"
# make sure it doesnt exist first
@@ -116,8 +116,11 @@ function teardown() {
@test 'split-out by default should fail if the output branch it wants to create already exists' {
repo_file_contents="
- remote_repo=\"..$SEP$test_remote_repo2\"
- include=\"test_remote_repo.txt\"
+ [repo]
+ remote = \"..$SEP$test_remote_repo2\"
+
+
+ include = \"test_remote_repo.txt\"
"
# make sure it doesnt exist first
diff --git a/test/general/usage.bats b/test/general/usage.bats
index 8f8b2ec..ef57c3e 100644
--- a/test/general/usage.bats
+++ b/test/general/usage.bats
@@ -16,7 +16,7 @@ function set_seperator() {
# I wanna use these tests for both windows (git bash)
# and linux, so I need to change the separator
if [[ -d /c/ ]]; then
- SEP="\\"
+ SEP="\\\\"
else
SEP="/"
fi
diff --git a/test/splitin/end-to-end.bats b/test/splitin/end-to-end.bats
index a2dad96..dff3431 100644
--- a/test/splitin/end-to-end.bats
+++ b/test/splitin/end-to-end.bats
@@ -25,7 +25,7 @@ function set_seperator() {
# I wanna use these tests for both windows (git bash)
# and linux, so I need to change the separator
if [[ -d /c/ ]]; then
- SEP="\\"
+ SEP="\\\\"
else
SEP="/"
fi
@@ -60,10 +60,10 @@ function teardown() {
@test 'can split in a remote_repo uri' {
repo_file_contents="
- remote_repo=\"..$SEP$test_remote_repo2\"
- include_as=(
- \"this/path/will/be/created/\" \" \"
- )
+ [repo]
+ remote = \"..$SEP$test_remote_repo2\"
+ [include_as]
+ \"this/path/will/be/created/\" = \" \"
"
echo "$repo_file_contents" > repo_file.sh
@@ -86,9 +86,8 @@ function teardown() {
@test 'can split in a local branch' {
repo_file_contents="
- include_as=(
- \"this/path/will/be/created/\" \" \"
- )
+ [include_as]
+ \"this/path/will/be/created/\" = \" \"
"
echo "$repo_file_contents" > repo_file.sh
@@ -117,10 +116,10 @@ function teardown() {
@test 'should not run if user has modified files' {
repo_file_contents="
- remote_repo=\"..$SEP$test_remote_repo2\"
- include_as=(
- \"lib/\" \" \"
- )
+ [repo]
+ remote = \"..$SEP$test_remote_repo2\"
+ [include_as]
+ \"lib/\" = \" \"
"
echo "$repo_file_contents" > repo_file.sh
@@ -148,10 +147,10 @@ function teardown() {
@test 'can split in to a specific output branch' {
repo_file_contents="
- remote_repo=\"..$SEP$test_remote_repo2\"
- include_as=(
- \"this/path/will/be/created/\" \" \"
- )
+ [repo]
+ remote = \"..$SEP$test_remote_repo2\"
+ [include_as]
+ \"this/path/will/be/created/\" = \" \"
"
echo "$repo_file_contents" > repo_file.sh
@@ -176,10 +175,10 @@ function teardown() {
@test 'can split in to a specific output branch (overrides repo_name)' {
repo_file_contents="
repo_name=\"abcd\"
- remote_repo=\"..$SEP$test_remote_repo2\"
- include_as=(
- \"this/path/will/be/created/\" \" \"
- )
+ [repo]
+ remote = \"..$SEP$test_remote_repo2\"
+ [include_as]
+ \"this/path/will/be/created/\" = \" \"
"
echo "$repo_file_contents" > repo_file.sh
@@ -192,11 +191,12 @@ function teardown() {
@test 'can split in a remote_repo with a specific remote_branch' {
repo_file_contents="
- remote_repo=\"..$SEP$test_remote_repo2\"
- remote_branch=\"test-branch\"
- include_as=(
- \"this/path/will/be/created/\" \" \"
- )
+ [repo]
+ remote = \"..$SEP$test_remote_repo2\"
+ branch=\"test-branch\"
+
+ [include_as]
+ \"this/path/will/be/created/\" = \" \"
"
echo "$repo_file_contents" > repo_file.sh
@@ -240,11 +240,11 @@ function teardown() {
cd "$curr_dir"
repo_file_contents="
- repo_name=\"doesnt_matter\"
- remote_repo=\"..$SEP$test_remote_repo2\"
- include_as=(
- \"locallib/\" \"lib/\"
- )
+ [repo]
+ remote = \"..$SEP$test_remote_repo2\"
+ name = \"doesnt_matter\"
+ [include_as]
+ \"locallib/\" = \"lib/\"
"
echo "$repo_file_contents" > repo_file.sh
@@ -279,11 +279,11 @@ function teardown() {
cd "$curr_dir"
repo_file_contents="
- repo_name=\"doesnt_matter\"
- remote_repo=\"..$SEP$test_remote_repo2\"
- include_as=(
- \"locallib/\" \"my lib/\"
- )
+ [repo]
+ name = \"doesnt_matter\"
+ remote = \"..$SEP$test_remote_repo2\"
+ [include_as]
+ \"locallib/\" = \"my lib/\"
"
echo "$repo_file_contents" > repo_file.sh
@@ -303,6 +303,42 @@ function teardown() {
[[ ! -f rootfile1.txt ]]
}
+@test 'can split in only n latest commits' {
+ # save current dir to cd back to later
+ curr_dir="$PWD"
+ # setup the test remote repo:
+ cd "$BATS_TMPDIR/test_remote_repo2"
+
+ echo "1a" > 1a.txt && git add 1a.txt && git commit -m "commit_1a"
+ echo "2b" > 2b.txt && git add 2b.txt && git commit -m "commit_2b"
+ echo "3c" > 3c.txt && git add 3c.txt && git commit -m "commit_3c"
+ cd "$curr_dir"
+
+ repo_file_contents="
+ [repo]
+ name=\"doesnt_matter\"
+ remote = \"..$SEP$test_remote_repo2\"
+ [include_as]
+ \"lib/\" = \" \"
+ "
+ echo "$repo_file_contents" > repo_file.sh
+
+ # we only want the latest 2 commits
+ run $PROGRAM_PATH split-in repo_file.sh --verbose --num-commits 2
+ echo "$output"
+ [[ $status == "0" ]]
+ git_branch=$(git branch --show-current)
+ git_log_now=$(git log --oneline)
+ num_commits="$(git log --oneline | wc -l)"
+ echo "$git_log_now"
+ echo "$git_branch"
+ [[ $git_branch == "doesnt_matter" ]]
+ # because n was 2, there should only be the top 2 commits
+ [[ $num_commits == "2" ]]
+ [[ $git_log_now != *"commit_1a"* ]]
+ [[ $git_log_now == *"commit_2b"* ]]
+ [[ $git_log_now == *"commit_3c"* ]]
+}
@test 'properly handles nested folder renames/moves' {
# save current dir to cd back to later
@@ -322,13 +358,13 @@ function teardown() {
cd "$curr_dir"
repo_file_contents="
- repo_name=\"doesnt_matter\"
- remote_repo=\"..$SEP$test_remote_repo2\"
- include_as=(
- \"lib/src/srcfile1.txt\" \"srcfile1.txt\"
- \"lib/src/\" \"lib/\"
- \"lib/\" \" \"
- )
+ [repo]
+ name=\"doesnt_matter\"
+ remote = \"..$SEP$test_remote_repo2\"
+ [include_as]
+ \"lib/src/srcfile1.txt\" = \"srcfile1.txt\"
+ \"lib/src/\" = \"lib/\"
+ \"lib/\" = \" \"
"
echo "$repo_file_contents" > repo_file.sh
@@ -365,9 +401,12 @@ function teardown() {
cd "$curr_dir"
repo_file_contents="
- repo_name=\"doesnt_matter\"
- remote_repo=\"..$SEP$test_remote_repo2\"
- include=\"lib/libfile1.txt\"
+ [repo]
+ name = \"doesnt_matter\"
+ remote = \"..$SEP$test_remote_repo2\"
+
+
+ include = \"lib/libfile1.txt\"
"
echo "$repo_file_contents" > repo_file.sh
@@ -404,9 +443,12 @@ function teardown() {
cd "$curr_dir"
repo_file_contents="
- repo_name=\"doesnt_matter\"
- remote_repo=\"..$SEP$test_remote_repo2\"
- include=(\"src/\" \"lib\")
+ [repo]
+ name=\"doesnt_matter\"
+ remote = \"..$SEP$test_remote_repo2\"
+
+
+ include=[\"src/\", \"lib\"]
"
echo "$repo_file_contents" > repo_file.sh
@@ -446,10 +488,13 @@ function teardown() {
cd "$curr_dir"
repo_file_contents="
- repo_name=\"doesnt_matter\"
- remote_repo=\"..$SEP$test_remote_repo2\"
- include=\"lib/\"
- exclude=\"lib/test/\"
+ [repo]
+ name = \"doesnt_matter\"
+ remote = \"..$SEP$test_remote_repo2\"
+
+
+ include = \"lib/\"
+ exclude = \"lib/test/\"
"
echo "$repo_file_contents" > repo_file.sh
@@ -489,8 +534,11 @@ function teardown() {
cd "$curr_dir"
repo_file_contents="
- remote_repo=\"..$SEP$test_remote_repo2\"
- include=\"lib/\"
+ [repo]
+ remote = \"..$SEP$test_remote_repo2\"
+
+
+ include = \"lib/\"
"
echo "$repo_file_contents" > repo_file.sh
@@ -528,8 +576,10 @@ function teardown() {
cd "$curr_dir"
repo_file_contents="
- remote_repo=\"..$SEP$test_remote_repo2\"
- include_as=(\"lib/\" \" \")
+ [repo]
+ remote = \"..$SEP$test_remote_repo2\"
+ [include_as]
+ \"lib/\" = \" \"
"
echo "$repo_file_contents" > repo_file.sh
@@ -558,8 +608,10 @@ function teardown() {
cd "$curr_dir"
repo_file_contents="
- remote_repo=\"..$SEP$test_remote_repo2\"
- include_as=(\"lib/\" \" \")
+ [repo]
+ remote = \"..$SEP$test_remote_repo2\"
+ [include_as]
+ \"lib/\" = \" \"
"
echo "$repo_file_contents" > repo_file.sh
@@ -591,8 +643,10 @@ function teardown() {
cd "$curr_dir"
repo_file_contents="
- remote_repo=\"..$SEP$test_remote_repo2\"
- include_as=(\"lib/\" \" \")
+ [repo]
+ remote = \"..$SEP$test_remote_repo2\"
+ [include_as]
+ \"lib/\" = \" \"
"
echo "$repo_file_contents" > repo_file.sh
@@ -610,8 +664,10 @@ function teardown() {
@test 'should not be able to use --topbase with --rebase' {
repo_file_contents="
- remote_repo=\"..$SEP$test_remote_repo2\"
- include_as=(\"lib/\" \" \")
+ [repo]
+ remote = \"..$SEP$test_remote_repo2\"
+ [include_as]
+ \"lib/\" = \" \"
"
echo "$repo_file_contents" > repo_file.sh
@@ -637,8 +693,11 @@ function teardown() {
cd "$curr_dir"
repo_file_contents="
- remote_repo=\"..$SEP$test_remote_repo2\"
- include=\"lib/\"
+ [repo]
+ remote = \"..$SEP$test_remote_repo2\"
+
+
+ include = \"lib/\"
"
echo "$repo_file_contents" > repo_file.sh
@@ -674,19 +733,22 @@ function teardown() {
echo "$(git log --oneline)"
repo_file_contents="
- remote_repo=\"..$SEP$test_remote_repo2\"
- include=\"file1.txt\"
+ [repo]
+ remote = \"..$SEP$test_remote_repo2\"
+
+
+ include = \"file1.txt\"
"
echo "$repo_file_contents" > repo_file.sh
git_log_before="$(git log --oneline)"
+ git_branch_before="$(git branch)"
run $PROGRAM_PATH split-in repo_file.sh -t --verbose
echo "$output"
echo "$(git log --oneline)"
git_log_now="$(git log --oneline)"
[[ $status == "0" ]]
- [[ $output == *"rebasing non-interactively"* ]]
- # it should still rebase because that will make the output
- # branch fast-forwardable
+ [[ $output == *"Nothing to topbase"* ]]
+ [[ "$(git branch)" == $git_branch_before ]]
[[ $git_log_now == $git_log_before ]]
}
@@ -706,19 +768,22 @@ function teardown() {
echo "$(git log --oneline)"
repo_file_contents="
- remote_repo=\"..$SEP$test_remote_repo2\"
- include=\"file1.txt\"
+ [repo]
+ remote = \"..$SEP$test_remote_repo2\"
+
+
+ include = \"file1.txt\"
"
echo "$repo_file_contents" > repo_file.sh
git_log_before="$(git log --oneline)"
+ git_branch_before="$(git branch)"
run $PROGRAM_PATH split-in repo_file.sh -t --verbose
echo "$output"
echo "$(git log --oneline)"
git_log_now="$(git log --oneline)"
[[ $status == "0" ]]
- [[ $output == *"rebasing non-interactively"* ]]
- # it should still rebase because that will make the output
- # branch fast-forwardable
+ [[ $output == *"Nothing to topbase"* ]]
+ [[ "$(git branch)" == $git_branch_before ]]
[[ $git_log_now == $git_log_before ]]
}
@@ -733,8 +798,11 @@ function teardown() {
echo "$(git log --oneline)"
repo_file_contents="
- remote_repo=\"..$SEP$test_remote_repo2\"
- include=\"file1.txt\"
+ [repo]
+ remote = \"..$SEP$test_remote_repo2\"
+
+
+ include = \"file1.txt\"
"
echo "$repo_file_contents" > repo_file.sh
git_log_before="$(git log --oneline)"
@@ -770,11 +838,11 @@ function teardown() {
# them, but i think it'd be too difficult due to multiple levels of
# escaping, and also it being treated differently on windows vs linux
repo_file_contents="
- remote_repo=\"..$SEP$test_remote_repo2\"
- include_as=(
- \"dumbfile.txt\" \"du[mbfile.txt\"
- \"spaghetti/\" \"dum'lib/\"
- )
+ [repo]
+ remote = \"..$SEP$test_remote_repo2\"
+ [include_as]
+ \"dumbfile.txt\" = \"du[mbfile.txt\"
+ \"spaghetti/\" = \"dum'lib/\"
"
echo "$repo_file_contents" > repo_file.sh
echo "repo file contents:"
diff --git a/test/splitinas/end-to-end.bats b/test/splitinas/end-to-end.bats
index 70ae5e9..b884e78 100644
--- a/test/splitinas/end-to-end.bats
+++ b/test/splitinas/end-to-end.bats
@@ -16,7 +16,7 @@ function set_seperator() {
# I wanna use these tests for both windows (git bash)
# and linux, so I need to change the separator
if [[ -d /c/ ]]; then
- SEP="\\"
+ SEP="\\\\"
else
SEP="/"
fi
@@ -180,8 +180,10 @@ function teardown() {
# with this when there are squash commits
@test 'can get latest changes using \"topbase\"' {
repo_file_contents="
- remote_repo=\"..$SEP$test_remote_repo2\"
- include_as=(\"lib/\" \" \")
+ [repo]
+ remote = \"..$SEP$test_remote_repo2\"
+ [include_as]
+ \"lib/\" = \" \"
"
echo "$repo_file_contents" > repo_file.sh
# the repo_file wont be committed
@@ -291,3 +293,17 @@ function teardown() {
[[ "$git_log_now" == *" N"* ]]
[[ "$git_log_now" == *" O"* ]]
}
+
+@test 'can generate a repo file' {
+ run $PROGRAM_PATH split-in-as "..$SEP$test_remote_repo2" --as abc/ --gen-repo-file --verbose
+ echo "$output"
+ [[ $status == "0" ]]
+ [[ "$(git branch --show-current)" == "test_remote_repo2" ]]
+ [[ -f test_remote_repo2.rf ]]
+ meta_rf_contents=$( repo_file.sh
@@ -82,7 +85,7 @@ function teardown() {
@test 'repo_file doesnt need a remote_repo if --output-branch provided' {
repo_file_contents="
- include=\"b.txt\"
+ include = \"b.txt\"
"
echo "$repo_file_contents" > repo_file.sh
@@ -99,8 +102,11 @@ function teardown() {
@test '--output-branch should override repo_name' {
repo_file_contents="
- include=\"b.txt\"
- repo_name=\"somename\"
+ [repo]
+ name = \"somename\"
+
+
+ include = \"b.txt\"
"
echo "$repo_file_contents" > repo_file.sh
@@ -117,8 +123,9 @@ function teardown() {
@test '--output-branch should override remote_repo' {
repo_file_contents="
- include=\"b.txt\"
- remote_repo=\"..$SEP$test_remote_repo2\"
+ include = \"b.txt\"
+ [repo]
+ remote = \"..$SEP$test_remote_repo2\"
"
echo "$repo_file_contents" > repo_file.sh
@@ -135,10 +142,10 @@ function teardown() {
@test 'should not run if user has modified files' {
repo_file_contents="
- remote_repo=\"..$SEP$test_remote_repo2\"
- include_as=(
- \"lib/\" \" \"
- )
+ [repo]
+ remote = \"..$SEP$test_remote_repo2\"
+ [include_as]
+ \"lib/\" = \" \"
"
echo "$repo_file_contents" > repo_file.sh
@@ -166,8 +173,11 @@ function teardown() {
@test 'capable of only including certain folders' {
repo_file_contents="
- remote_repo=\"..$SEP$test_remote_repo2\"
- include=\"a\"
+ [repo]
+ remote = \"..$SEP$test_remote_repo2\"
+
+
+ include = \"a\"
"
echo "$repo_file_contents" > repo_file.sh
@@ -201,11 +211,14 @@ function teardown() {
@test 'works for both folders and files' {
repo_file_contents="
- remote_repo=\"..$SEP$test_remote_repo2\"
- include=(
- \"a\"
- \"b/b1.txt\"
- )
+ [repo]
+ remote = \"..$SEP$test_remote_repo2\"
+
+
+ include = [
+ \"a\",
+ \"b/b1.txt\",
+ ]
"
echo "$repo_file_contents" > repo_file.sh
@@ -241,10 +254,13 @@ function teardown() {
@test 'works for recursive folders' {
repo_file_contents="
- remote_repo=\"..$SEP$test_remote_repo2\"
- include=(
+ [repo]
+ remote = \"..$SEP$test_remote_repo2\"
+
+
+ include = [
\"a/a1\"
- )
+ ]
"
echo "$repo_file_contents" > repo_file.sh
@@ -287,11 +303,10 @@ function teardown() {
@test 'can split out to a specific output branch' {
repo_file_contents="
- remote_repo=\"..$SEP$test_remote_repo2\"
- include_as=(
- \"a.txt\"
- \"new_a.txt\"
- )
+ [repo]
+ remote = \"..$SEP$test_remote_repo2\"
+ [include_as]
+ \"a.txt\" = \"new_a.txt\"
"
echo "$repo_file_contents" > repo_file.sh
@@ -319,11 +334,10 @@ function teardown() {
@test 'can only include_as a single file' {
repo_file_contents="
- remote_repo=\"..$SEP$test_remote_repo2\"
- include_as=(
- \"a.txt\"
- \"new_a.txt\"
- )
+ [repo]
+ remote = \"..$SEP$test_remote_repo2\"
+ [include_as]
+ \"a.txt\" = \"new_a.txt\"
"
echo "$repo_file_contents" > repo_file.sh
@@ -350,11 +364,10 @@ function teardown() {
@test 'can only include_as a single folder' {
repo_file_contents="
- remote_repo=\"..$SEP$test_remote_repo2\"
- include_as=(
- \"a\"
- \"new_a\"
- )
+ [repo]
+ remote = \"..$SEP$test_remote_repo2\"
+ [include_as]
+ \"a\" = \"new_a\"
"
echo "$repo_file_contents" > repo_file.sh
@@ -389,11 +402,10 @@ function teardown() {
@test 'can only include_as a single to root' {
repo_file_contents="
- remote_repo=\"..$SEP$test_remote_repo2\"
- include_as=(
- \"a/\"
- \" \"
- )
+ [repo]
+ remote = \"..$SEP$test_remote_repo2\"
+ [include_as]
+ \"a/\" =\" \"
"
echo "$repo_file_contents" > repo_file.sh
@@ -426,12 +438,11 @@ function teardown() {
@test 'can include_as to rename a nested folder but keep everything else' {
repo_file_contents="
- remote_repo=\"..$SEP$test_remote_repo2\"
- include=\"a\"
- include_as=(
- \"a/old_a\"
- \"a/new_a\"
- )
+ include = \"a\"
+ [repo]
+ remote = \"..$SEP$test_remote_repo2\"
+ [include_as]
+ \"a/old_a\" = \"a/new_a\"
"
echo "$repo_file_contents" > repo_file.sh
@@ -463,12 +474,16 @@ function teardown() {
@test 'can include_as include and exclude a specific directory structure' {
repo_file_contents="
- remote_repo=\"..$SEP$test_remote_repo2\"
- include_as=(\"a/a1\" \"lib\")
- exclude=(
- \"a/a1/b\"
+ [repo]
+ remote = \"..$SEP$test_remote_repo2\"
+ [include_as]
+ \"a/a1\" = \"lib\"
+
+
+ exclude = [
+ \"a/a1/b\",
\"a/a1/a1.txt\"
- )
+ ]
"
echo "$repo_file_contents" > repo_file.sh
@@ -505,8 +520,9 @@ function teardown() {
# from test_remote_repo, we split out the file test_remote_repo.txt
# and into a repo called test_remote_repo2:
repo_file_contents="
- remote_repo=\"..$SEP$test_remote_repo2\"
- include=\"test_remote_repo.txt\"
+ include = \"test_remote_repo.txt\"
+ [repo]
+ remote = \"..$SEP$test_remote_repo2\"
"
echo "$repo_file_contents" > repo_file.sh
@@ -525,8 +541,9 @@ function teardown() {
@test 'can optionally rebase new branch onto original' {
repo_file_contents="
- remote_repo=\"..$SEP$test_remote_repo2\"
- include=(\"lib/\" \"test_remote_repo.txt\")
+ include = [\"lib/\", \"test_remote_repo.txt\"]
+ [repo]
+ remote = \"..$SEP$test_remote_repo2\"
"
echo "$repo_file_contents" > repo_file.sh
@@ -556,9 +573,10 @@ function teardown() {
@test 'can rebase onto specific remote branch' {
repo_file_contents="
- remote_repo=\"..$SEP$test_remote_repo2\"
- remote_branch=\"specific-branch\"
- include=(\"lib/\" \"test_remote_repo.txt\")
+ include = [\"lib/\", \"test_remote_repo.txt\"]
+ [repo]
+ remote = \"..$SEP$test_remote_repo2\"
+ branch = \"specific-branch\"
"
echo "$repo_file_contents" > repo_file.sh
@@ -598,9 +616,10 @@ function teardown() {
# here we also test that passing the cli arg will override
# whats defined in the repo file
repo_file_contents="
- remote_repo=\"..$SEP$test_remote_repo2\"
- remote_branch=\"specific-branch\"
- include=(\"lib/\" \"test_remote_repo.txt\")
+ include = [\"lib/\", \"test_remote_repo.txt\"]
+ [repo]
+ remote = \"..$SEP$test_remote_repo2\"
+ branch = \"specific-branch\"
"
echo "$repo_file_contents" > repo_file.sh
@@ -649,9 +668,10 @@ function teardown() {
# here we also test that passing the cli arg will override
# whats defined in the repo file
repo_file_contents="
- remote_repo=\"..$SEP$test_remote_repo2\"
- remote_branch=\"specific-branch\"
- include=(\"lib/\" \"test_remote_repo.txt\")
+ include = [\"lib/\", \"test_remote_repo.txt\"]
+ [repo]
+ remote = \"..$SEP$test_remote_repo2\"
+ branch = \"specific-branch\"
"
echo "$repo_file_contents" > repo_file.sh
@@ -698,9 +718,10 @@ function teardown() {
@test 'gives proper error if failed to find remote_branch' {
repo_file_contents="
- remote_repo=\"..$SEP$test_remote_repo2\"
- remote_branch=\"specific-branch\"
- include=(\"lib/\" \"test_remote_repo.txt\")
+ include = [\"lib/\", \"test_remote_repo.txt\"]
+ [repo]
+ remote = \"..$SEP$test_remote_repo2\"
+ branch = \"specific-branch\"
"
echo "$repo_file_contents" > repo_file.sh
@@ -715,8 +736,9 @@ function teardown() {
@test 'rebasing new branch onto original should not leave temporary branch' {
repo_file_contents="
- remote_repo=\"..$SEP$test_remote_repo2\"
- include=(\"lib/\" \"test_remote_repo.txt\")
+ include = [\"lib/\", \"test_remote_repo.txt\"]
+ [repo]
+ remote = \"..$SEP$test_remote_repo2\"
"
echo "$repo_file_contents" > repo_file.sh
@@ -745,8 +767,9 @@ function teardown() {
@test 'can topbase new branch onto original branch' {
repo_file_contents="
- remote_repo=\"..$SEP$test_remote_repo2\"
- include=(\"lib/\")
+ include = [\"lib/\"]
+ [repo]
+ remote = \"..$SEP$test_remote_repo2\"
"
echo "$repo_file_contents" > repo_file.sh
@@ -789,8 +812,9 @@ function teardown() {
@test '--topbase should not say success if there were rebase merge conflicts' {
repo_file_contents="
- remote_repo=\"..$SEP$test_remote_repo2\"
- include=(\"lib/\")
+ include = [\"lib/\"]
+ [repo]
+ remote = \"..$SEP$test_remote_repo2\"
"
echo "$repo_file_contents" > repo_file.sh
@@ -817,8 +841,9 @@ function teardown() {
@test '--topbase should not say success if there were rebase merge conflicts (case it takes all)' {
repo_file_contents="
- remote_repo=\"..$SEP$test_remote_repo2\"
- include=(\"lib/\")
+ include = [\"lib/\"]
+ [repo]
+ remote = \"..$SEP$test_remote_repo2\"
"
echo "$repo_file_contents" > repo_file.sh
@@ -843,8 +868,9 @@ function teardown() {
@test '--topbase should add a branch label before rebasing' {
repo_file_contents="
- remote_repo=\"..$SEP$test_remote_repo2\"
- include=(\"lib/\")
+ include = [\"lib/\"]
+ [repo]
+ remote = \"..$SEP$test_remote_repo2\"
"
echo "$repo_file_contents" > repo_file.sh
@@ -889,8 +915,9 @@ function teardown() {
@test '--rebase should not say success if there were rebase merge conflicts' {
repo_file_contents="
- remote_repo=\"..$SEP$test_remote_repo2\"
- include=(\"lib/\")
+ include = [\"lib/\"]
+ [repo]
+ remote = \"..$SEP$test_remote_repo2\"
"
echo "$repo_file_contents" > repo_file.sh
diff --git a/test/splitoutas/end-to-end.bats b/test/splitoutas/end-to-end.bats
index 04f1b95..73c8f48 100644
--- a/test/splitoutas/end-to-end.bats
+++ b/test/splitoutas/end-to-end.bats
@@ -16,7 +16,7 @@ function set_seperator() {
# I wanna use these tests for both windows (git bash)
# and linux, so I need to change the separator
if [[ -d /c/ ]]; then
- SEP="\\"
+ SEP="\\\\"
else
SEP="/"
fi
diff --git a/test/test.yml b/test/test.yml
index bb1029f..e527f2b 100644
--- a/test/test.yml
+++ b/test/test.yml
@@ -6,7 +6,7 @@ run_unit_tests:
- name: cargo test
run: |
if [[ "${{ custom.should_run_git_tests }}" != "false" ]]; then
- cargo test --features gittest
+ cargo test --features gittests
else
cargo test
fi
diff --git a/test/topbase/end-to-end.bats b/test/topbase/end-to-end.bats
index bce6f27..430d007 100644
--- a/test/topbase/end-to-end.bats
+++ b/test/topbase/end-to-end.bats
@@ -16,7 +16,7 @@ function set_seperator() {
# I wanna use these tests for both windows (git bash)
# and linux, so I need to change the separator
if [[ -d /c/ ]]; then
- SEP="\\"
+ SEP="\\\\"
else
SEP="/"
fi
@@ -244,6 +244,8 @@ function teardown() {
}
@test 'can detect delete commits' {
+ # this test is whether or not mgt can
+ # detect a delete commit as a topbase fork point,
git checkout -b top_branch
# make commits that would separate top_branch from master
echo "q" > q.txt && git add q.txt && git commit -m "_q"
@@ -271,7 +273,11 @@ function teardown() {
echo "git log after:"
echo "$git_log_after_topbase"
current_branch="$(git branch --show-current)"
- [[ "$current_branch" == "top_branch" ]]
+
+ # the behavior is to go back to base branch
+ # if nothing to topbase... for some reason...
+ # maybe change this later. too lazy for now
+ [[ "$current_branch" == "master" ]]
[[ "$git_log_after_topbase" != "$git_log_before_topbase" ]]
[[ $status == 0 ]]
[[ "$git_log_after_topbase" == *"_a"* ]]