From 1dc3d2bbdaf3e41ea851b3cfb143147d749b40fa Mon Sep 17 00:00:00 2001 From: "Kyle J. McKay" Date: Sun, 25 Jun 2017 22:07:25 -0700 Subject: [PATCH] tg: add wayback machine Building on the "tg tag" + "tg revert" functionality, allow any operation to be performed in the context of a "wayback" state. Signed-off-by: Kyle J. McKay --- .gitignore | 3 + README | 167 ++++++++++- t/t2000-hook-installation.sh | 19 +- t/t4110-wayback-summary.sh | 658 +++++++++++++++++++++++++++++++++++++++++++ tg-shell.sh | 96 +++++++ tg.sh | 274 +++++++++++++++--- 6 files changed, 1150 insertions(+), 67 deletions(-) create mode 100755 t/t4110-wayback-summary.sh create mode 100755 tg-shell.sh diff --git a/.gitignore b/.gitignore index b347919..e85a4e8 100644 --- a/.gitignore +++ b/.gitignore @@ -90,6 +90,9 @@ /tg-revert /tg-revert.txt /tg-revert.html +/tg-shell +/tg-shell.txt +/tg-shell.html /tg-status.txt /tg-status.html /tg-summary diff --git a/README b/README index 2a68cb2..f35f7c6 100644 --- a/README +++ b/README @@ -15,17 +15,18 @@ patch and providing some tools to maintain the branches. See also: - :REQUIREMENTS_: Installation requirements - :SYNOPSIS_: Command line example session - :USAGE_: Command line details - :`NO UNDO`_: Where's the undo!!! - :CONVENTIONS_: Suggestions for organizing your TopGit branches - :`EXTRA SETTINGS`_: Various possible "topgit.*" config settings - :ALIASES_: Git-like TopGit command aliases - :NAVIGATION_: Getting around with "next" and "prev" - :GLOSSARY_: All the TopGit vocabulary in one place - :TECHNICAL_: How it works behind the scenes - :`TESTING TOPGIT`_: How to run the TopGit test suite + :REQUIREMENTS_: Installation requirements + :SYNOPSIS_: Command line example session + :USAGE_: Command line details + :`NO UNDO`_: Where's the undo!!! + :CONVENTIONS_: Suggestions for organizing your TopGit branches + :`EXTRA SETTINGS`_: Various possible "topgit.*" config settings + :ALIASES_: Git-like TopGit command aliases + :NAVIGATION_: Getting around with "next" and "prev" + :`WAYBACK MACHINE`_: Turn back the clock and then come back + :GLOSSARY_: All the TopGit vocabulary in one place + :TECHNICAL_: How it works behind the scenes + :`TESTING TOPGIT`_: How to run the TopGit test suite REQUIREMENTS @@ -361,12 +362,58 @@ command unless an explicit ``--stash`` option is given. If you are likely to ever want to undo a ``tg update``, setting ``topgit.autostash`` to ``false`` is highly discouraged! +Note that if you have foolishly disabled the autostash functionality and +suddenly find yourself in an emergency "WHERE'S THE UNDO???" situation you +*may* be able to use the special ``TG_STASH`` ref. But only if you're quick. +It's only set if you've foolishly disabled autostash and it always overwrites +the previous ``TG_STASH`` value if there was one (there's no reflog for it) +and it will most likely *not* survive a ``git gc`` (even an automatic one) no +matter what gc expiration values are used. + Note that the tags saved by ``tg tag --stash`` are stored in the ``refs/tgstash`` ref and its reflog. Unfortunately, while Git is happy to maintain the reflog (once it's been enabled which ``tg tag`` guarantees for ``refs/tgstash``), Git is unable to view an annotated/signed tag's reflog! -Instead Git dereferences the tag and shows the wrong thing. Use the -``tg tag -g`` command to view the ``refs/tgstash`` reflog instead. +Instead Git dereferences the tag and shows the wrong thing. + +Use the ``tg tag -g`` command to view the ``refs/tgstash`` reflog instead. + + +WAYBACK MACHINE +--------------- + +After reading about `NO UNDO`_ and the `tg tag`_ command used to provide a +semblance of undo in some cases, you have the foundation to understand the +wayback machine. + +The "wayback machine" provides a way to go back to a previous ref state as +stored in a TopGit tag created by `tg tag`_. It actually normally returns to a +hybrid state as it does not prune (unless you prefix the wayback tag with +a ``:``). In other words, any refs that have been newly created since the +target tag was made will continue to exist in the "wayback" view of things +(unless you used a pruning wayback tag -- one prefixed with a ``:``). + +Any operations that are read-only and do not require working tree files (e.g. +the ``-i`` or ``-w`` options of `tg patch`_) are allowed using the wayback +machine. Simply add a global ``-w `` option to the command. + +This functionality can be extremely useful for quickly examing/querying a +previous state recorded some time ago with a `tg tag`_. + +As the wayback machine uses a separate caching area, except initial operations +to be less speedy, but repeated wayback operations on the same wayback tag +should happen at normal speed. + +One new command exists expressly for use with the wayback machine. + +The `tg shell`_ command will spawn an interactive shell or run a specific shell +command in a temporary writable and non-bare repository that has its ref +namespace set to the (possibly pruned if it's a pruning wayback tag) wayback +tag's view of the world. This pretty much lifts all wayback restrictions, but +read the description for `tg shell`_ for more details. There is an option +available to specify the location where this "temporary" directory is created +thereby allowing it to persist, but the same warnings then apply as using the +``git clone --shared`` command. EXTRA SETTINGS @@ -797,6 +844,7 @@ Global options: -r Pretend ``topgit.remote`` is set to -u Pretend ``topgit.remote`` is not set -c Pass config option to git, may be repeated + -w Activate `wayback machine`_ using the `tg tag`_ --no-pager Disable use of any pager (by both TopGit and Git) --pager Enable use of a pager (aka ``-p``) --top-bases Show full ``top-bases`` ref prefix and exit @@ -827,6 +875,7 @@ The ``tg`` tool has several subcommands: :`tg rebase`_: Auto continue git rebase if rerere resolves conflicts :`tg remote`_: Set up remote for fetching/pushing TopGit branches :`tg revert`_: Revert ref(s) to a state stored in a ``tg tag`` + :`tg shell`_: Extended `wayback machine`_ mode :`tg status`_: Show current TopGit status (e.g. in-progress update) :`tg summary`_: Show various information about TopGit branches :`tg tag`_: Create tag that records current TopGit branch state @@ -2060,6 +2109,98 @@ tg revert tree will always be left completely untouched (and the reflog for the pointed-to ref can always be used to find the previous value). +tg shell +~~~~~~~~ + Enter extended `wayback machine`_ mode. + + The global ``-w `` option must be specified (but as a special + case for the ``shell`` subcommand a destination of ``:`` may be + used to get a shell with no wayback ref changes). + + The "" value must be the name of a tag created by (or known to) + `tg tag`_. However, it may also have a ``:`` prefixed to it to + indicate that it should prune (making it into a "pruning wayback tag"). + Use of a "pruning wayback tag" results in a repository that contains + excludsively those refs listed in the specified tag. Otherwise the + wayback repository will just revert those refs while keeping the others + untouched (the default behavior). + + The `wayback machine`_ activates as normal for the specified + destination but then a new ``${SHELL:-/bin/sh}`` is spawned in a + temporary non-bare repository directory that shares all the same + objects from the repository but has its own copy of the ref namespace + where the refs specified in the wayback destination have all been + changed to have their wayback values. + + If any arguments are given a POSIX shell will be spawned instead + concatenating all the arguments together with a space and passing + them to it via a ``-c`` option. If ``-q`` (or ``--quote``) is given + then each argument will first be separately "quoted" to protect it from + the shell allowing something like this:: + + tg -w shell -q git for-each-ref --format="%(refname)" + + to work without needing to manually add the extra level of quoting that + would otherwise be required due to the parentheses. + + Most of the repository configuration will be inherited, but some + will be overridden for safety and for convenience. All "gc" activity + within the wayback repository will be suppressed to avoid accidents + (i.e. no auto gc will run and "gc" commands will complain and not run). + + Override and/or bypass this safety protection at your own peril! + Especially *do not run* the ``git prune`` plumbing command in the + wayback repository! If you do so (or bypass any of the other safties) + be prepared for corruption and loss of data in the repository. + Just *don't do that* in the first place! + + Using `git wayback-tag` will show the tag used to enter the wayback + machine. Using `git wayback-updates` will show ref changes that have + occurred since the wayback tag was created (it will not show refs that + have since been created unless a pruning wayback tag was used). + Finally, ``git wayback-repository`` will show the home repository but + so will ``git remote -v`` in the output displayed for the ``wayback`` + remote. + + The special ``wayback`` remote refers to the original repository and + can be used to push ref changes back to it. Note, however, that all + default push refspecs are disabled for safety and an explicit refspec + will need to be used to do so. + + Unlike the normal `wayback machine`_ mode, ``HEAD`` will be detached + to a new commit with an empty tree that contains the message and author + from the wayback tag used. This prevents ugly status displays while + avoiding the need to checkout any files into the temporary working + tree. The parent of this commit will, however, be set to the wayback + tag's commit making it easy to access if desired. + + Also unlike the normal `wayback machine`_ mode, there are no + limitations on what can be done in the temporary repository. + And since it will be non-bare and writable, commands that may not have + been allowed in the original repository will work too. + + When the shell spawned by this subcommand exits, the temporary wayback + repository and all newly created objects and ref changes made in it, if + any, *will be lost*. If work has been done in it that needs to be + saved, it must be pushed somewhere (even if only back to the original + repository using the special ``wayback`` remote). + + Lastly there's the ``--directory`` option. If the ``--directory`` + option is used the temporary "wayback repository" will be created at + the specified location (which must either not exist or must be an empty + directory -- no force option available this time as too many things + could easily go wrong in that case). If the ``--directory`` option is + used then the "wayback repository" *will persist* after ``tg shell`` + completes allowing it to continue to be used! Be warned though, all + the same warnings that apply to ``git clone --shared`` apply to such + a repository. If it's created using a ``tgstash`` tag those warnings + are especially salient. Use a single argument of either ``:`` (to + just create with no output) or ``pwd`` (to show the full absolute path + to the new "wayback repository") when using the ``--directory`` option + if the sole purpose is just to create the wayback repository for use. + Note that the ``--directory`` option *must* be listed as the first + option after the ``shell`` subcommand name if used. + tg prev ~~~~~~~ Output the "previous" branch(es) in the patch series containing the diff --git a/t/t2000-hook-installation.sh b/t/t2000-hook-installation.sh index 7352620..aed1113 100755 --- a/t/t2000-hook-installation.sh +++ b/t/t2000-hook-installation.sh @@ -90,6 +90,7 @@ push rebase remote revert +shell status summary tag @@ -100,7 +101,7 @@ version tg_cmd_will_setup() { case "$1" in --version|--status|--hooks-path|--exec-path|--awk-path|--top-bases| \ - base|contains|files|info|log|mail|next|patch|prev|rebase|revert|status|st|summary|tag|version) + base|contains|files|info|log|mail|next|patch|prev|rebase|revert|shell|status|st|summary|tag|version) return 1 esac return 0 @@ -115,8 +116,8 @@ test_expect_success 'no setup happens for help' ' test_might_fail tg --bogus && has_no_tg_setup && for cmd in $TG_CMDS; do say "# checking tg $cmd help variations" && - test_might_fail tg $cmd -h && has_no_tg_setup && - test_might_fail tg $cmd --help && has_no_tg_setup + test_might_fail "$sqt" || return + <"$sqt" tr -s "$tab" " " +} + +topbases="$(tg --top-bases)" && test -n "$topbases" || die 'no top-bases!' + +test_expect_success 'setup' ' + test_create_repo pristine && cd pristine && + git checkout --orphan rootcommit && + git read-tree --empty && + test_tick && + git commit --allow-empty -m "empty root commit" && + git tag rootcommit && + tg_test_create_tag t/root && + git checkout --orphan branch1 && + git read-tree --empty && + test_commit branch1-start && + git checkout --orphan branch2 && + git read-tree --empty && + test_commit branch2-start && + git checkout --orphan branch3 && + git read-tree --empty && + test_commit branch3-start && + reset_repo && + git clean -x -d -f && + tg_test_create_tag t/branches && + tg_test_create_tag t/branches-only "refs/heads/branch*" && + tg_test_create_branches <<-EOT && + rootbare bare empty root + :: + + rootmsg topmsg root + :~ + + rootdeps --no-topmsg topdeps root + : + + root standard root branch + : + + annihilated annihilated branch + :: + + basebare bare base + ::rootcommit + + +basebare bare base + :::basebare + + t/branch1 branch1 topgit + branch1 + + t/branch2 branch2 topgit + branch2 + + t/branch3 branch3 topgit + branch3 + EOT + tg_test_create_tag t/midway && + tg_test_create_branches <<-EOT && + reused-1level1 reused with one tg branch below + t/branch1 + + reused-2level1 reused with two tg branches below + t/branch1 + t/branch2 + + reused-1level2 reused with one tg branch below + reused-1level1 + + reused-2level2 reused with two tg branches below + reused-1level1 + reused-2level1 + + reused-multi multi-level reuse + reused-2level1 + t/branch3 + reused-2level2 + EOT + tg_test_create_tag t/reused && + tg_test_create_branches <<-EOT && + boring just a boring commit here move along + ::: + + t/subjmiss + boring + + t/subjmt + boring + EOT + tg_test_create_tag t/boring1 refs/heads/boring "refs/heads/t/subj*" "$topbases/t/subj*" && + tg_test_create_tag t/all1 && + git checkout -f annihilated && + test_tick && + git commit --allow-empty -m "annihilated not empty" && + git checkout -f t/branch2 && + <.topmsg grep -v -i subject >.topmsg2 && + mv -f .topmsg2 .topmsg && + printf "%s\n" "subject:[PATCH] branch2" " topgit" >> .topmsg && + git add .topmsg && + test_tick && + git commit -m ".topmsg: no space after subject colon" && + git checkout -f t/subjmiss && + <.topmsg grep -v -i subject >.topmsg2 && + mv -f .topmsg2 .topmsg && + git add .topmsg && + test_tick && + git commit -m ".topmsg: whoops, no subject: here" && + git checkout -f t/subjmt && + <.topmsg grep -v -i subject >.topmsg2 && + mv -f .topmsg2 .topmsg && + printf "%s\n" "subJECT: " >> .topmsg && + git add .topmsg && + test_tick && + git commit -m ".topmsg: subject of emptyness" && + tg_test_create_tag t/boring2 refs/heads/boring "refs/heads/t/subj*" "$topbases/t/subj*" && + tg_test_create_tag t/all2 && + reset_repo && + git clean -x -d -f && + cd .. && + cp -pR pristine copy +' + +printf "%s" "\ +refs/heads/annihilated +refs/heads/basebare +refs/heads/boring +refs/heads/branch1 +refs/heads/branch2 +refs/heads/branch3 +refs/heads/reused-1level1 +refs/heads/reused-1level2 +refs/heads/reused-2level1 +refs/heads/reused-2level2 +refs/heads/reused-multi +refs/heads/root +refs/heads/rootbare +refs/heads/rootcommit +refs/heads/rootdeps +refs/heads/rootmsg +refs/heads/t/branch1 +refs/heads/t/branch2 +refs/heads/t/branch3 +refs/heads/t/subjmiss +refs/heads/t/subjmt +" > all_refsh || die failed to make all_refsh + +printf "%s" "\ +$topbases/annihilated +$topbases/basebare +$topbases/reused-1level1 +$topbases/reused-1level2 +$topbases/reused-2level1 +$topbases/reused-2level2 +$topbases/reused-multi +$topbases/root +$topbases/rootbare +$topbases/rootdeps +$topbases/rootmsg +$topbases/t/branch1 +$topbases/t/branch2 +$topbases/t/branch3 +$topbases/t/subjmiss +$topbases/t/subjmt +" > all_refsb || die failed to make all_refsb + +printf "%s" "\ +refs/tags/branch1-start +refs/tags/branch2-start +refs/tags/branch3-start +refs/tags/rootcommit +refs/tags/t/all1 +refs/tags/t/all2 +refs/tags/t/boring1 +refs/tags/t/boring2 +refs/tags/t/branches +refs/tags/t/branches-only +refs/tags/t/midway +refs/tags/t/reused +refs/tags/t/root +" > all_refst || die failed to make all_refst + +if test "$topbases" = "refs/top-bases"; then + cat all_refsh all_refst all_refsb > all_refs +else + cat all_refsh all_refsb all_refst > all_refs +fi || die failed to make all_refs + +printf "%s" "\ +refs/heads/annihilated +refs/heads/basebare +refs/heads/boring +refs/heads/branch1 +refs/heads/branch2 +refs/heads/branch3 +refs/heads/reused-1level1 +refs/heads/reused-1level2 +refs/heads/reused-2level1 +refs/heads/reused-2level2 +refs/heads/reused-multi +refs/heads/root +refs/heads/rootbare +refs/heads/rootcommit +refs/heads/rootdeps +refs/heads/rootmsg +refs/heads/t/branch1 +refs/heads/t/branch2 +refs/heads/t/branch3 +refs/heads/t/subjmiss +refs/heads/t/subjmt +$topbases/annihilated +$topbases/basebare +$topbases/reused-1level1 +$topbases/reused-1level2 +$topbases/reused-2level1 +$topbases/reused-2level2 +$topbases/reused-multi +$topbases/root +$topbases/rootbare +$topbases/rootdeps +$topbases/rootmsg +$topbases/t/branch1 +$topbases/t/branch2 +$topbases/t/branch3 +$topbases/t/subjmiss +$topbases/t/subjmt +" > all_tg_refs || die failed to make all_tg_refs + +printf "%s" "\ +refs/heads/annihilated +refs/heads/basebare +refs/heads/branch1 +refs/heads/branch2 +refs/heads/branch3 +refs/heads/root +refs/heads/rootbare +refs/heads/rootcommit +refs/heads/rootdeps +refs/heads/rootmsg +refs/heads/t/branch1 +refs/heads/t/branch2 +refs/heads/t/branch3 +$topbases/annihilated +$topbases/basebare +$topbases/root +$topbases/rootbare +$topbases/rootdeps +$topbases/rootmsg +$topbases/t/branch1 +$topbases/t/branch2 +$topbases/t/branch3 +" > midway_refs || die failed to make midway_refs + +printf "%s" "\ +refs/heads/annihilated +refs/heads/basebare +refs/heads/branch1 +refs/heads/branch2 +refs/heads/branch3 +refs/heads/reused-1level1 +refs/heads/reused-1level2 +refs/heads/reused-2level1 +refs/heads/reused-2level2 +refs/heads/reused-multi +refs/heads/root +refs/heads/rootbare +refs/heads/rootcommit +refs/heads/rootdeps +refs/heads/rootmsg +refs/heads/t/branch1 +refs/heads/t/branch2 +refs/heads/t/branch3 +$topbases/annihilated +$topbases/basebare +$topbases/reused-1level1 +$topbases/reused-1level2 +$topbases/reused-2level1 +$topbases/reused-2level2 +$topbases/reused-multi +$topbases/root +$topbases/rootbare +$topbases/rootdeps +$topbases/rootmsg +$topbases/t/branch1 +$topbases/t/branch2 +$topbases/t/branch3 +" > reused_refs || die failed to make reused_refs + +printf "%s" "\ +refs/heads/boring +refs/heads/t/subjmiss +refs/heads/t/subjmt +$topbases/t/subjmiss +$topbases/t/subjmt +" > boring_refs || die failed to make boring_refs + +squish printf "%s" "\ +annihilated branch annihilated (annihilated) +basebare branch basebare (bare branch) +reused-1level1 [PATCH] reused with one tg branch below +reused-1level2 [PATCH] reused with one tg branch below +reused-2level1 [PATCH] reused with two tg branches below +reused-2level2 [PATCH] reused with two tg branches below +reused-multi [PATCH] multi-level reuse +root [PATCH] standard root branch +rootbare branch rootbare (no commits) +rootdeps branch rootdeps (missing .topmsg) +rootmsg [PATCH] topmsg root +t/branch1 [PATCH] branch1 topgit +t/branch2 [PATCH] branch2 topgit +t/branch3 [PATCH] branch3 topgit +t/subjmiss branch t/subjmiss (missing \"Subject:\" in .topmsg) +t/subjmt branch t/subjmt (empty \"Subject:\" in .topmsg) +" > pristine_full_list || die failed to make pristine_full_list + +squish printf "%s" "\ +annihilated branch annihilated (annihilated) +basebare branch basebare (bare branch) +reused-1level1 [PATCH] reused with one tg branch below +reused-1level2 [PATCH] reused with one tg branch below +reused-2level1 [PATCH] reused with two tg branches below +reused-2level2 [PATCH] reused with two tg branches below +reused-multi [PATCH] multi-level reuse +root [PATCH] standard root branch +rootbare branch rootbare (no commits) +rootdeps branch rootdeps (missing .topmsg) +rootmsg [PATCH] topmsg root +t/branch1 [PATCH] branch1 topgit +t/branch2 [PATCH] branch2 topgit +t/branch3 [PATCH] branch3 topgit +t/subjmiss [PATCH] branch t/subjmiss +t/subjmt [PATCH] branch t/subjmt +" > pristine_boring_list || die failed to make pristine_boring_list + +squish printf "%s" "\ +annihilated branch annihilated (no commits) +basebare branch basebare (bare branch) +reused-1level1 [PATCH] reused with one tg branch below +reused-1level2 [PATCH] reused with one tg branch below +reused-2level1 [PATCH] reused with two tg branches below +reused-2level2 [PATCH] reused with two tg branches below +reused-multi [PATCH] multi-level reuse +root [PATCH] standard root branch +rootbare branch rootbare (no commits) +rootdeps branch rootdeps (missing .topmsg) +rootmsg [PATCH] topmsg root +t/branch1 [PATCH] branch1 topgit +t/branch2 [PATCH] branch2 topgit +t/branch3 [PATCH] branch3 topgit +t/subjmiss branch t/subjmiss (missing \"Subject:\" in .topmsg) +t/subjmt branch t/subjmt (empty \"Subject:\" in .topmsg) +" > pristine_fullb4_list || die failed to make pristine_fullb4_list + +squish printf "%s" "\ +annihilated branch annihilated (no commits) +basebare branch basebare (bare branch) +reused-1level1 [PATCH] reused with one tg branch below +reused-1level2 [PATCH] reused with one tg branch below +reused-2level1 [PATCH] reused with two tg branches below +reused-2level2 [PATCH] reused with two tg branches below +reused-multi [PATCH] multi-level reuse +root [PATCH] standard root branch +rootbare branch rootbare (no commits) +rootdeps branch rootdeps (missing .topmsg) +rootmsg [PATCH] topmsg root +t/branch1 [PATCH] branch1 topgit +t/branch2 [PATCH] branch2 topgit +t/branch3 [PATCH] branch3 topgit +t/subjmiss [PATCH] branch t/subjmiss +t/subjmt [PATCH] branch t/subjmt +" > pristine_boringb4_list || die failed to make pristine_boring_list + +squish printf "%s" "\ +annihilated branch annihilated (no commits) +basebare branch basebare (bare branch) +reused-1level1 [PATCH] reused with one tg branch below +reused-1level2 [PATCH] reused with one tg branch below +reused-2level1 [PATCH] reused with two tg branches below +reused-2level2 [PATCH] reused with two tg branches below +reused-multi [PATCH] multi-level reuse +root [PATCH] standard root branch +rootbare branch rootbare (no commits) +rootdeps branch rootdeps (missing .topmsg) +rootmsg [PATCH] topmsg root +t/branch1 [PATCH] branch1 topgit +t/branch2 [PATCH] branch2 topgit +t/branch3 [PATCH] branch3 topgit +t/subjmiss branch t/subjmiss (missing \"Subject:\" in .topmsg) +t/subjmt branch t/subjmt (empty \"Subject:\" in .topmsg) +" > pristine_fullb4b1_list || die failed to make pristine_fullb4b1_list + +squish printf "%s" "\ +annihilated branch annihilated (no commits) +basebare branch basebare (bare branch) +root [PATCH] standard root branch +rootbare branch rootbare (no commits) +rootdeps branch rootdeps (missing .topmsg) +rootmsg [PATCH] topmsg root +t/branch1 [PATCH] branch1 topgit +t/branch2 [PATCH] branch2 topgit +t/branch3 [PATCH] branch3 topgit +" > pristine_midway_list || die failed to make pristine_midway_list + +squish printf "%s" "\ +annihilated branch annihilated (no commits) +basebare branch basebare (bare branch) +reused-1level1 [PATCH] reused with one tg branch below +reused-1level2 [PATCH] reused with one tg branch below +reused-2level1 [PATCH] reused with two tg branches below +reused-2level2 [PATCH] reused with two tg branches below +reused-multi [PATCH] multi-level reuse +root [PATCH] standard root branch +rootbare branch rootbare (no commits) +rootdeps branch rootdeps (missing .topmsg) +rootmsg [PATCH] topmsg root +t/branch1 [PATCH] branch1 topgit +t/branch2 [PATCH] branch2 topgit +t/branch3 [PATCH] branch3 topgit +" > pristine_reused_list || die failed to make pristine_reused_list + +squish printf "%s" "\ +t/subjmiss [PATCH] branch t/subjmiss +t/subjmt [PATCH] branch t/subjmt +" > pristine_boring1_list || die failed to make pristine_boring1_list + +squish printf "%s" "\ +t/subjmiss branch t/subjmiss (missing \"Subject:\" in .topmsg) +t/subjmt branch t/subjmt (empty \"Subject:\" in .topmsg) +" > pristine_boring2_list || die failed to make pristine_boring2_list + +squish printf "%s" "\ + basebare branch basebare (bare branch) + 0 reused-1level1 [PATCH] reused with one tg branch below + 0 reused-1level2 [PATCH] reused with one tg branch below + 0 D * reused-2level1 [PATCH] reused with two tg branches below + 0 D * reused-2level2 [PATCH] reused with two tg branches below + 0 D reused-multi [PATCH] multi-level reuse + 0 root [PATCH] standard root branch + 0 rootdeps branch rootdeps (missing .topmsg) + 0 rootmsg [PATCH] topmsg root + 0 t/branch1 [PATCH] branch1 topgit + 0 * t/branch2 [PATCH] branch2 topgit + 0 * t/branch3 [PATCH] branch3 topgit + 0 t/subjmiss branch t/subjmiss (missing \"Subject:\" in .topmsg) + 0 t/subjmt branch t/subjmt (empty \"Subject:\" in .topmsg) +" > pristine_full_summary || die failed to make pristine_full_summary + +squish printf "%s" "\ + basebare branch basebare (bare branch) + 0 reused-1level1 [PATCH] reused with one tg branch below + 0 reused-1level2 [PATCH] reused with one tg branch below + 0 D * reused-2level1 [PATCH] reused with two tg branches below + 0 D * reused-2level2 [PATCH] reused with two tg branches below + 0 D reused-multi [PATCH] multi-level reuse + 0 root [PATCH] standard root branch + 0 rootdeps branch rootdeps (missing .topmsg) + 0 rootmsg [PATCH] topmsg root + 0 t/branch1 [PATCH] branch1 topgit + 0 * t/branch2 [PATCH] branch2 topgit + 0 * t/branch3 [PATCH] branch3 topgit + 0 t/subjmiss [PATCH] branch t/subjmiss + 0 t/subjmt [PATCH] branch t/subjmt +" > pristine_boring_summary || die failed to make pristine_boring_summary + +squish printf "%s" "\ + basebare branch basebare (bare branch) + 0 root [PATCH] standard root branch + 0 rootdeps branch rootdeps (missing .topmsg) + 0 rootmsg [PATCH] topmsg root + 0 t/branch1 [PATCH] branch1 topgit + 0 t/branch2 [PATCH] branch2 topgit + 0 t/branch3 [PATCH] branch3 topgit +" > pristine_midway_summary || die failed to make pristine_midway_summary + +squish printf "%s" "\ + basebare branch basebare (bare branch) + 0 reused-1level1 [PATCH] reused with one tg branch below + 0 reused-1level2 [PATCH] reused with one tg branch below + 0 D * reused-2level1 [PATCH] reused with two tg branches below + 0 D * reused-2level2 [PATCH] reused with two tg branches below + 0 D reused-multi [PATCH] multi-level reuse + 0 root [PATCH] standard root branch + 0 rootdeps branch rootdeps (missing .topmsg) + 0 rootmsg [PATCH] topmsg root + 0 t/branch1 [PATCH] branch1 topgit + 0 * t/branch2 [PATCH] branch2 topgit + 0 * t/branch3 [PATCH] branch3 topgit +" > pristine_reused_summary || die failed to make pristine_reused_summary + +squish printf "%s" "\ + 0 t/subjmiss [PATCH] branch t/subjmiss + 0 t/subjmt [PATCH] branch t/subjmt +" > pristine_boring1_summary || die failed to make pristine_boring1_summary + +squish printf "%s" "\ + 0 t/subjmiss branch t/subjmiss (missing \"Subject:\" in .topmsg) + 0 t/subjmt branch t/subjmt (empty \"Subject:\" in .topmsg) +" > pristine_boring2_summary || die failed to make pristine_boring2_summary + +test_expect_success 'verify list' ' + squish tg -C copy summary -vvl >actual && + test_diff pristine_full_list actual && + squish tg -C copy -w : summary -vvl > actual && + test_diff pristine_full_list actual && + squish tg -C copy -w : summary -vvl > actual && + test_diff pristine_full_list actual && + squish tg -C copy -w : shell tg summary -vvl > actual && + test_diff pristine_full_list actual && + squish tg -C copy -w t/all2 summary -vvl > actual && + test_diff pristine_full_list actual && + squish tg -C copy -w t/all2 shell tg summary -vvl> actual && + test_diff pristine_full_list actual +' + +test_expect_success 'verify summary' ' + squish tg -C copy summary >actual && + test_diff pristine_full_summary actual && + squish tg -C copy -w : summary > actual && + test_diff pristine_full_summary actual && + squish tg -C copy -w : summary > actual && + test_diff pristine_full_summary actual && + squish tg -C copy -w : shell tg summary > actual && + test_diff pristine_full_summary actual && + squish tg -C copy -w t/all2 summary > actual && + test_diff pristine_full_summary actual && + squish tg -C copy -w t/all2 shell tg summary > actual && + test_diff pristine_full_summary actual +' + +test_expect_success 'wayback list' ' + >expected && + for wtag in t/root t/branches t/branches-only; do + squish tg -C copy -w "$wtag" summary -vvl >actual && + test_diff pristine_full_list actual && + tg -C copy -w ":$wtag" summary -vvl >actual && + test_diff expected actual + done && + squish tg -C copy -w t/midway summary -vvl >actual && + test_diff pristine_fullb4_list actual && + squish tg -C copy -w :t/midway summary -vvl >actual && + test_diff pristine_midway_list actual && + squish tg -C copy -w t/reused summary -vvl >actual && + test_diff pristine_fullb4_list actual && + squish tg -C copy -w :t/reused summary -vvl >actual && + test_diff pristine_reused_list actual && + squish tg -C copy -w t/boring1 summary -vvl >actual && + test_diff pristine_boring_list actual && + squish tg -C copy -w :t/boring1 summary -vvl >actual && + test_diff pristine_boring1_list actual && + squish tg -C copy -w t/all1 summary -vvl >actual && + test_diff pristine_boringb4_list actual && + squish tg -C copy -w :t/all1 summary -vvl >actual && + test_diff pristine_boringb4_list actual && + squish tg -C copy -w t/boring2 summary -vvl >actual && + test_diff pristine_full_list actual && + squish tg -C copy -w :t/boring2 summary -vvl >actual && + test_diff pristine_boring2_list actual +' + +test_expect_success 'wayback summary' ' + >expected && + for wtag in t/root t/branches t/branches-only; do + squish tg -C copy -w "$wtag" summary >actual && + test_diff pristine_full_summary actual && + tg -C copy -w ":$wtag" summary >actual && + test_diff expected actual + done && + squish tg -C copy -w t/midway summary >actual && + test_diff pristine_full_summary actual && + squish tg -C copy -w :t/midway summary >actual && + test_diff pristine_midway_summary actual && + squish tg -C copy -w t/reused summary >actual && + test_diff pristine_full_summary actual && + squish tg -C copy -w :t/reused summary >actual && + test_diff pristine_reused_summary actual && + squish tg -C copy -w t/boring1 summary >actual && + test_diff pristine_boring_summary actual && + squish tg -C copy -w :t/boring1 summary >actual && + test_diff pristine_boring1_summary actual && + squish tg -C copy -w t/all1 summary >actual && + test_diff pristine_boring_summary actual && + squish tg -C copy -w :t/all1 summary >actual && + test_diff pristine_boring_summary actual && + squish tg -C copy -w t/boring2 summary >actual && + test_diff pristine_full_summary actual && + squish tg -C copy -w :t/boring2 summary >actual && + test_diff pristine_boring2_summary actual +' + +test_expect_success 'wayback refs' ' + printf "%s\n" "refs/heads/rootcommit" >expected && + tg -C copy -w t/root shell git for-each-ref --format="%\\(refname\\)" > actual && + test_diff all_refs actual && + tg -C copy -w :t/root shell git for-each-ref --format="%\\(refname\\)" > actual && + test_diff expected actual && + printf "%s\n" "refs/heads/branch1" "refs/heads/branch2" "refs/heads/branch3" \ + "refs/heads/rootcommit" >expected && + tg -C copy -w t/branches shell -q git for-each-ref --format="%(refname)" > actual && + test_diff all_refs actual && + tg -C copy -w :t/branches shell -q git for-each-ref --format="%(refname)" > actual && + test_diff expected actual && + printf "%s\n" "refs/heads/branch1" "refs/heads/branch2" "refs/heads/branch3" >expected && + tg -C copy -w t/branches-only shell -q git for-each-ref --format="%(refname)" > actual && + test_diff all_refs actual && + tg -C copy -w :t/branches-only shell -q git for-each-ref --format="%(refname)" > actual && + test_diff expected actual && + tg -C copy -w t/midway shell -q git for-each-ref --format="%(refname)" > actual && + test_diff all_refs actual && + tg -C copy -w :t/midway shell -q git for-each-ref --format="%(refname)" > actual && + test_diff midway_refs actual && + tg -C copy -w t/reused shell -q git for-each-ref --format="%(refname)" > actual && + test_diff all_refs actual && + tg -C copy -w :t/reused shell -q git for-each-ref --format="%(refname)" > actual && + test_diff reused_refs actual && + tg -C copy -w t/all1 shell -q git for-each-ref --format="%(refname)" > actual && + test_diff all_refs actual && + tg -C copy -w :t/all1 shell -q git for-each-ref --format="%(refname)" > actual && + test_diff all_tg_refs actual && + tg -C copy -w t/boring1 shell -q git for-each-ref --format="%(refname)" > actual && + test_diff all_refs actual && + tg -C copy -w :t/boring1 shell -q git for-each-ref --format="%(refname)" > actual && + test_diff boring_refs actual && + tg -C copy -w t/all2 shell -q git for-each-ref --format="%(refname)" > actual && + test_diff all_refs actual && + tg -C copy -w :t/all2 shell -q git for-each-ref --format="%(refname)" > actual && + test_diff all_tg_refs actual && + tg -C copy -w t/boring2 shell -q git for-each-ref --format="%(refname)" > actual && + test_diff all_refs actual && + tg -C copy -w :t/boring2 shell -q git for-each-ref --format="%(refname)" > actual && + test_diff boring_refs actual +' + +test_expect_success 'wayback directory refs' ' + tg -C copy -w :t/midway shell --directory "$PWD/newdir" : && + git -C newdir for-each-ref --format="%(refname)" > actual && + test_diff midway_refs actual +' + +test_done diff --git a/tg-shell.sh b/tg-shell.sh new file mode 100755 index 0000000..02cfa2a --- /dev/null +++ b/tg-shell.sh @@ -0,0 +1,96 @@ +#!/bin/sh +# TopGit wayback machine shell command +# (C) 2017 Kyle J. McKay +# All rights reserved +# GPLv2 + +USAGE="\ +Usage: ${tgname:-tg} [...] -w [:] shell [--directory=] [-q] [--] [...]" + +usage() +{ + if [ "${1:-0}" != 0 ]; then + printf '%s\n' "$USAGE" >&2 + else + printf '%s\n' "$USAGE" + fi + exit ${1:-0} +} + +quote= + +while [ $# -gt 0 ]; do case "$1" in + -h|--help|--directory|--directory=*) + usage + ;; + --directory|--directory=*) + # only one is allowed and it should have been handled by tg.sh + # which means this is the second one and it's a usage error + usage 1 + ;; + --quote|-q) + quote=1 + ;; + --) + shift + break + ;; + -?*) + echo "Unknown option: $1" >&2 + usage 1 + ;; + *) + break + ;; +esac; shift; done +[ -n "$wayback" ] || usage 1 + +# Everything's been handled by tg.sh except for verifying a TTY +# is present for an interactive shell + +cmd= +redir= +theshell=@SHELL_PATH@ +if [ $# -eq 0 ]; then + [ -z "$SHELL" ] || theshell="$SHELL" + test -t 0 || die "cannot use interactive wayback shell on non-TTY STDIN" + if test -t 1; then + test -t 2 || redir='2>&1' + elif test -t 2; then + redir='>&2' + else + die "cannot use interactive wayback shell on non-TTY STDOUT/STDERR" + fi + PS1='[../${PWD##*/}] wayback$ ' && export PS1 + wbname="${wayback#:}" + [ -n "$wbname" ] || wbname='now?!' + eval 'info "going wayback to $wbname..."' "$redir" +else + if [ -z "$quote" ]; then + cmd='-c "$*"' + else + # attempt to "quote" the arguments and then glue them together + cmdstr= + for cmdword in "$@"; do + cmdworddq=1 + case "$cmdword" in [A-Za-z_]*) + if test z"${cmdword%%[!A-Za-z_0-9]*}" = z"$cmdword" + then + cmdworddq= + else case "$cmdword" in *=*) + cmdvar="${cmdword%%=*}" + if test z"${cmdvar%%[!A-Za-z_0-9]*}" = z"$cmdvar" + then + v_quotearg cmdword "${cmdword#*=}" + cmdword="$cmdvar=$cmdword" + cmdworddq= + fi + esac; fi + esac + test z"$cmdworddq" = z || v_quotearg cmdword "$cmdword" + cmdstr="${cmdstr:+$cmdstr }$cmdword" + done + cmd='-c "$cmdstr"' + fi +fi +eval '"$theshell"' "$cmd" "$redir" diff --git a/tg.sh b/tg.sh index 6cf5196..0fa1434 100644 --- a/tg.sh +++ b/tg.sh @@ -1053,8 +1053,11 @@ become_non_cacheable() } # call this to make sure the current Git repository has an associated work tree +# also make sure we are not in wayback mode ensure_work_tree() { + [ -z "$wayback" ] || + die "the wayback machine cannot be used with the specified options" setup_git_dir_is_bare [ -n "$git_dir_is_bare" ] || return 0 die "This operation must be run in a work tree" @@ -1534,7 +1537,7 @@ do_help() echo "TopGit version $TG_VERSION - A different patch queue manager" echo "Usage: $tgname [-C ] [-r | -u]" \ - "[-c =] [--[no-]pager|-p] ($cmds) ..." + "[-c =] [--[no-]pager|-p] [-w [:]] ($cmds) ..." echo " Or: $tgname help [-w] []" echo "Use \"$tgdisplaydir$tgname help tg\" for overview of TopGit" elif [ -r "$TG_INST_CMDDIR"/tg-$1 -o -r "$TG_INST_SHAREDIR/tg-$1.txt" ] ; then @@ -1972,32 +1975,9 @@ basic_setup() oldbases="$topbases" } -## Initial setup -initial_setup() +tmpdir_setup() { - # suppress the merge log editor feature since git 1.7.10 - - GIT_MERGE_AUTOEDIT=no - export GIT_MERGE_AUTOEDIT - - basic_setup $1 - iowopt= - ! vcmp "$git_version" '>=' "2.5" || iowopt="--ignore-other-worktrees" - gcfbopt= - ! vcmp "$git_version" '>=' "2.6" || gcfbopt="--buffer" - auhopt= - ! vcmp "$git_version" '>=' "2.9" || auhopt="--allow-unrelated-histories" - v_get_show_cdup root_dir - root_dir="${root_dir:-.}" - logrefupdates="$(git config --bool core.logallrefupdates 2>/dev/null)" || : - [ "$logrefupdates" = "true" ] || logrefupdates= - - # make sure root_dir doesn't end with a trailing slash. - - root_dir="${root_dir%/}" - - # create global temporary directories, inside GIT_DIR - + [ -z "$tg_tmp_dir" ] || return 0 if [ -n "$TG_TMPDIR" ] && [ -d "$TG_TMPDIR" ] && [ -w "$TG_TMPDIR" ] && { >"$TG_TMPDIR/.check"; } >/dev/null 2>&1; then tg_tmp_dir="$TG_TMPDIR" @@ -2015,17 +1995,20 @@ initial_setup() [ -n "$tg_tmp_dir" ] || [ -z "$TMPDIR" ] || tg_tmp_dir="$(mktemp -d "/tmp/tg-tmp.XXXXXX" 2>/dev/null)" || tg_tmp_dir= [ -z "$tg_tmp_dir" ] || tg_tmp_dir="$(cd "$tg_tmp_dir" && pwd -P)" fi - unset_ TG_TMPDIR + [ -n "$tg_tmp_dir" ] && [ -w "$tg_tmp_dir" ] && { >"$tg_tmp_dir/.check"; } >/dev/null 2>&1 || + die "could not create a writable temporary directory" + + # whenever tg_tmp_dir is != "" these must always be set tg_ref_cache="$tg_tmp_dir/tg~ref-cache" tg_ref_cache_br="$tg_ref_cache.br" tg_ref_cache_rbr="$tg_ref_cache.rbr" tg_ref_cache_ann="$tg_ref_cache.ann" tg_ref_cache_dep="$tg_ref_cache.dep" - [ -n "$tg_tmp_dir" ] && [ -w "$tg_tmp_dir" ] && { >"$tg_ref_cache"; } >/dev/null 2>&1 || - die "could not create a writable temporary directory" - - # make sure global cache directory exists inside GIT_DIR or $tg_tmp_dir +} +cachedir_setup() +{ + [ -z "$tg_cache_dir" ] || return 0 user_id_no="$(id -u)" || : : "${user_id_no:=_99_}" tg_cache_dir="$git_common_dir/tg-cache" @@ -2041,6 +2024,17 @@ initial_setup() [ -n "$tg_cache_dir" ] || die "could not create a writable tg-cache directory (even a temporary one)" + if [ -n "$2" ]; then + # allow the wayback machine to share a separate cache + [ -d "$tg_cache_dir/wayback" ] || mkdir "$tg_cache_dir/wayback" >/dev/null 2>&1 || : + ! [ -d "$tg_cache_dir/wayback" ] || ! { >"$tg_cache_dir/wayback/.tgcache"; } >/dev/null 2>&1 || + tg_cache_dir="$tg_cache_dir/wayback" + fi +} + +# set up alternate deb dirs +altodb_setup() +{ # GIT_ALTERNATE_OBJECT_DIRECTORIES can contain double-quoted entries # since Git v2.11.1; however, it's only necessary for : (or perhaps ;) # so we avoid it if possible and require v2.11.1 to do it at all @@ -2052,25 +2046,27 @@ initial_setup() # same repository we were invoked on tg_use_alt_odb=1 - _odbdir="${GIT_OBJECT_DIRECTORY:-$git_common_dir/objects}" - [ -n "$_odbdir" ] && [ -d "$_odbdir" ] || tg_use_alt_odb= - _fulltmpdir= - [ -z "$tg_use_alt_odb" ] || _fulltmpdir="$(cd "$tg_tmp_dir" && pwd -P)" - case "$_fulltmpdir" in *[";:"]*|'"'*) vcmp "$git_version" '>=' "2.11.1" || tg_use_alt_odb=; esac _fullodbdir= - [ -z "$tg_use_alt_odb" ] || _fullodbdir="$(cd "$_odbdir" && pwd -P)" - if [ -n "$tg_use_alt_odb" ] && [ -n "$TG_OBJECT_DIRECTORY" ] && [ -d "$TG_OBJECT_DIRECTORY/info" ] && + _odbdir="${GIT_OBJECT_DIRECTORY:-$git_common_dir/objects}" + [ -n "$_odbdir" ] && [ -d "$_odbdir" ] && _fullodbdir="$(cd "$_odbdir" && pwd -P)" || + die "could not find objects directory" + if [ -n "$TG_OBJECT_DIRECTORY" ] && [ -d "$TG_OBJECT_DIRECTORY/info" ] && [ -f "$TG_OBJECT_DIRECTORY/info/alternates" ] && [ -r "$TG_OBJECT_DIRECTORY/info/alternates" ]; then if IFS= read -r _otherodbdir <"$TG_OBJECT_DIRECTORY/info/alternates" && [ -n "$_otherodbdir" ] && [ "$_otherodbdir" = "$_fullodbdir" ]; then tg_use_alt_odb=2 fi fi + _fulltmpdir="$(cd "$tg_tmp_dir" && pwd -P)" if [ "$tg_use_alt_odb" = "1" ]; then # create an alternate objects database to keep the ephemeral objects in mkdir -p "$tg_tmp_dir/objects/info" - echol "$_fullodbdir" >"$tg_tmp_dir/objects/info/alternates" TG_OBJECT_DIRECTORY="$_fulltmpdir/objects" + [ "$_fullodbdir" = "$TG_OBJECT_DIRECTORY" ] || + echol "$_fullodbdir" >"$tg_tmp_dir/objects/info/alternates" + fi + case "$_fulltmpdir" in *[";:"]*|'"'*) vcmp "$git_version" '>=' "2.11.1" || tg_use_alt_odb=; esac + if [ "$tg_use_alt_odb" = "1" ]; then case "$TG_OBJECT_DIRECTORY" in *[";:"]*|'"'*) # surround in "..." and backslash-escape internal '"' and '\\' @@ -2109,6 +2105,150 @@ noalt_setup() unset_ TG_TMPDIR TG_OBJECT_DIRECTORY TG_PRESERVED_ALTERNATES tg_use_alt_odb } +## Initial setup +initial_setup() +{ + # suppress the merge log editor feature since git 1.7.10 + + GIT_MERGE_AUTOEDIT=no + export GIT_MERGE_AUTOEDIT + + basic_setup $1 + iowopt= + ! vcmp "$git_version" '>=' "2.5" || iowopt="--ignore-other-worktrees" + gcfbopt= + ! vcmp "$git_version" '>=' "2.6" || gcfbopt="--buffer" + auhopt= + ! vcmp "$git_version" '>=' "2.9" || auhopt="--allow-unrelated-histories" + v_get_show_cdup root_dir + root_dir="${root_dir:-.}" + logrefupdates="$(git config --bool core.logallrefupdates 2>/dev/null)" || : + [ "$logrefupdates" = "true" ] || logrefupdates= + + # make sure root_dir doesn't end with a trailing slash. + + root_dir="${root_dir%/}" + + # create global temporary and cache directories, usually inside GIT_DIR + + tmpdir_setup + unset_ TG_TMPDIR + cachedir_setup + + # the wayback machine directory serves as its own "altodb" + [ -n "$wayback" ] || altodb_setup +} + +activate_wayback_machine() +{ + [ -n "${1#:}" ] || [ -n "$2" ] || { wayback=; return 0; } + setup_git_dirs + tmpdir_setup + altodb_setup + tgwbr= + tgwbr2= + if [ -n "${1#:}" ]; then + tgwbr="$(get_temp wbinfo)" + tgwbr2="${tgwbr}2" + tg revert --list --no-short "${1#:}" >"$tgwbr" && test -s "$tgwbr" || return 1 + # tg revert will likely leave a revert-tag-only cache which is not what we want + remove_ref_cache + fi + cachedir_setup "" 1 # use a separate wayback cache dir + # but don't step on the normal one if the separate one could not be set up + case "$tg_cache_dir" in */wayback);;*) tg_cache_dir=; esac + altodb="$TG_OBJECT_DIRECTORY" + if [ -n "$3" ] && [ -n "$2" ]; then + [ -d "$3" ] || { mkdir -p "$3" && [ -d "$3" ]; } || + die "could not create wayback directory: $3" + tg_wayback_dir="$(cd "$3" && pwd -P)" || die "could not get wayback directory full path" + [ -d "$tg_wayback_dir/.git" ] || { mkdir -p "$tg_wayback_dir/.git" && [ -d "$tg_wayback_dir/.git" ]; } || + die "could not initialize wayback directory: $3" + is_empty_dir "$tg_wayback_dir" ".git" && is_empty_dir "$tg_wayback_dir/.git" "." || + die "wayback directory is not empty: $3" + mkdir "$tg_wayback_dir/.git/objects" + mkdir "$tg_wayback_dir/.git/objects/info" + cat "$altodb/info/alternates" >"$tg_wayback_dir/.git/objects/info/alternates" + else + tg_wayback_dir="$tg_tmp_dir/wayback" + mkdir "$tg_wayback_dir" + mkdir "$tg_wayback_dir/.git" + ln -s "$altodb" "$tg_wayback_dir/.git/objects" + fi + mkdir "$tg_wayback_dir/.git/refs" + printf '0 Wayback Machine' >"$tg_wayback_dir/.git/gc.pid" + qpesc="$(printf '%s\n' "$git_common_dir" | sed -e 's/\([\\""]\)/\\\1/g' -e '$!s/$/\\n/' | tr -d '\n')" + laru="false" + [ -z "$2" ] || laru="true" + printf '%s' "\ +[include] + path = \"$qpesc/config\" +[core] + bare = false + logAllRefUpdates = $laru + repositoryFormatVersion = 0 +[extensions] + preciousObjects = true +[gc] + auto = 0 + autoDetach = false + autoPackLimit = 0 + packRefs = false +[remote \"wayback\"] + url = \"$qpesc\" +[push] + default = nothing + followTags = true +[alias] + wayback-updates = fetch --force --no-tags --dry-run wayback refs/*:refs/* +" >"$tg_wayback_dir/.git/config" + cat "$git_dir/HEAD" >"$tg_wayback_dir/.git/HEAD" + case "$1" in ":"?*);;*) + git show-ref >"$tg_wayback_dir/.git/packed-refs" + git --git-dir="$tg_wayback_dir/.git" pack-refs --all + esac + noalt_setup + TG_OBJECT_DIRECTORY="$altodb" && export TG_OBJECT_DIRECTORY + if [ -n "${1#:}" ]; then + <"$tgwbr" sed 's/^\([^ ][^ ]*\) \([^ ][^ ]*\)$/update \2 \1/' | + git --git-dir="$tg_wayback_dir/.git" update-ref -m "wayback to $1" ${2:+--create-reflog} --stdin + fi + if test -n "$2"; then + # extra setup for potential shell + qpesc2="$(printf '%s\n' "$git_common_dir" | sed -e 's/\([\\""]\)/\\\\\1/g' -e '$!s/$/\\n/' | tr -d '\n')" + printf '\twayback-repository = "!printf '\''%%s\\\\n'\'' \\"%s\\""\n' "$qpesc2" >>"$tg_wayback_dir/.git/config" + qtesc="$(printf '%s\n' "${1:-:}" | sed 's/\([""]\)/\\\1/g')" + printf '\twayback-tag = "!printf '\''%%s\\\\n'\'' \\"%s\\""\n' "$qtesc" >>"$tg_wayback_dir/.git/config" + if [ -d "$git_common_dir/rr-cache" ]; then + ln -s "$git_common_dir/rr-cache" "$tg_wayback_dir/.git/rr-cache" + printf "[rerere]\n\tenabled = true\n" >>"$tg_wayback_dir/.git/config" + fi + wbauth= + wbprnt= + if [ -n "${1#:}" ]; then + [ -n "$tgwbr2" ] || tgwbr2="$(get_temp wbtag)" + git --git-dir="$git_common_dir" cat-file tag "${1#:}" >"$tgwbr2" || return 1 + wbprnt="${lf}parent $(git --git-dir="$git_common_dir" rev-parse --verify --quiet "${1#:}"^0 -- 2>/dev/null)" || wbprnt= + wbauth="$(<"$tgwbr2" awk '{if(!$0)exit;if($1=="tagger")print "author" substr($0,7)}')" + fi + wbcmtr="committer Wayback Machine <-> $(date "+%s %z")" + [ -n "$wbauth" ] || wbauth="author${wbcmtr#committer}" + wbtree="$(git --git-dir="$tg_wayback_dir/.git" mktree "$tg_wayback_dir/.git/HEAD" + fi + cd "$tg_wayback_dir" + unset git_dir git_common_dir +} + set_topbases() { # refer to "top-bases" in a refname with $topbases @@ -2319,6 +2459,7 @@ else gitcdopt= noremote= forcepager= + wayback= cmd= while :; do case "$1" in @@ -2416,6 +2557,21 @@ else export GIT_CONFIG_PARAMETERS shift;; + -w) + if [ -n "$wayback" ]; then + echo "Option -w may be used at most once." >&2 + do_help + exit 1 + fi + shift + if [ -z "$1" ]; then + echo "Option -w requires an argument." >&2 + do_help + exit 1 + fi + wayback="$1" + shift;; + --) shift break;; @@ -2490,7 +2646,10 @@ else exit $do_topbases_help fi git_dir= - ! git_dir="$(git rev-parse --git-dir 2>&1)" || setup_git_dirs + if git_dir="$(git rev-parse --git-dir 2>&1)"; then + [ -z "$wayback" ] || activate_wayback_machine "$wayback" + setup_git_dirs + fi set_topbases if [ -n "$show_remote_topbases" ]; then basic_setup_remote @@ -2529,6 +2688,8 @@ else TG_ALIAS_DEPTH="$looplevel" export TG_ALIAS_DEPTH if [ "!${tgalias#?}" = "$tgalias" ]; then + [ -z "$wayback" ] || + die "-w is not allowed before an '!' alias command" unset_ GIT_PREFIX if pfx="$(git rev-parse --show-prefix 2>/dev/null)"; then GIT_PREFIX="$pfx" @@ -2537,7 +2698,7 @@ else cd "./$(git rev-parse --show-cdup 2>/dev/null)" exec @SHELL_PATH@ -c "${tgalias#?} \"\$@\"" @SHELL_PATH@ "$@" else - eval 'exec "$tgbin"' "$tgalias" '"$@"' + eval 'exec "$tgbin"' "${wayback:+-w \"\$wayback\"}" "$tgalias" '"$@"' fi die "alias execution failed for: $tgalias" } @@ -2548,16 +2709,39 @@ else showing_help=1 fi - [ -n "$showing_help" ] || initial_setup - [ -z "$noremote" ] || unset_ base_remote - nomergesetup="$showing_help" - case "$cmd" in base|contains|files|info|log|mail|next|patch|prev|rebase|revert|summary|tag) + case "$cmd" in base|contains|files|info|log|mail|next|patch|prev|rebase|revert|shell|summary|tag) # avoid merge setup where not necessary nomergesetup=1 esac + if [ -n "$wayback" ] && [ -z "$showing_help" ]; then + [ -n "$nomergesetup" ] || + die "the wayback machine cannot be used with the \"$cmd\" subcommand" + if [ "$cmd" = "shell" ]; then + # this is ugly; `tg shell` should handle this but it's too + # late there so we have to do it here + wayback_dir= + case "$1" in + "--directory="?*) + wayback_dir="${1#--directory=}" && shift;; + "--directory=") + die "--directory requires an argument";; + "--directory") + [ $# -ge 2 ] || die "--directory requires an argument" + wayback_dir="$2" && shift 2;; + esac + activate_wayback_machine "$wayback" 1 "$wayback_dir" + else + activate_wayback_machine "$wayback" + fi || + die "failed to set the wayback machine to target \"$wayback\"" + fi + + [ -n "$showing_help" ] || initial_setup + [ -z "$noremote" ] || unset_ base_remote + if [ -z "$nomergesetup" ]; then # make sure merging the .top* files will always behave sanely -- 2.11.4.GIT