How to convert this script into a custom mercurial command? - bash

I have the following script:
#!/bin/bash
if [ $# -ne 2 ]; then
echo -n "$0 - a utility for applying uncommitted changes to a "
echo "remote hg repository locally also"
echo "Usage: $0 user#hostname path/to/repository"
exit -1
fi
user_at_hostname="$1"
remote_path="$2"
ssh "$user_at_hostname" hg -R "$remote_path" diff | hg import --no-commit -
It's not the most glorious piece of code, and I would rather do something more "mercurial" than that, so to speak. Specifically, I was wondering whether I could achieve the same using a mercurial alias / custom command. Can I?
PS - I had also thought about maybe issuing some sort of shelve command on the remote repository instead of just getting a diff, but I don't want to make thing too complicated.

If you just want to convert this script into an hg foo command without changing it, use a shell alias. Just copy the last line and replace the custom variables with $1 and $2.
If you want to make this look more like a "normal" Mercurial workflow, you could start by committing the changes and then pulling them. I imagine that you are avoiding this workflow so that you can change your mind about these modifications without polluting your repository's history with "oops" commits. If that is the case, then you will likely be interested in the Evolve extension. The Evolve extension is intended to provide a safe and reasonably well-behaved system for sharing mutable history. In this way, you can commit a change; share it with another repository; amend, rebase, squash, or otherwise modify the change; and then share the modified changeset with the same repository or a different one. You can also prune changesets from history and share the fact that you pruned them. If this sharing causes problems, such as amending a commit which has descendants, Mercurial will detect those problems and offer a fix (e.g. rebase the descendants onto the new version of the commit) which you can execute automatically with hg evolve. While the extension is still experimental, it does basically work for most simple use cases.
If experimental software isn't of interest to you, you can still flag the repository as non-publishing. This will allow you to use more traditional history-editing machinery such as hg rebase, hg histedit, and hg strip even after you have pushed to the repository. However, revisions which are destroyed in one repository will not automatically vanish from other repositories without the evolve extension. You will have to strip them by hand.
Finally, note that hg push --force does not destroy revisions. It creates new anonymous branches, typically resulting in a messy history, but without actually losing any data. It is different from git in this fashion.

Related

git branch command works fine as a cli command, but fails when run from loop or script using variables

In creating setup scripts, I have several git repos that I clone locally. This is done through a temporarily available proxy that may or may not be available later on, so I need to create all the remote branches from the remote repo as local branches that can be switched to. I have a method to extract the names of the remote repos that I want, when get stored as
[user]$ nvVar=$(git branch -r | grep -v '\->' | grep -Ev 'master|spdk\-1\.6' | cut -d'/' -f2)
This gives me variable list that can be iterated through, containing the branches I need to bring down.
[user]$ echo "$nvVar"
lightnvm
nvme-cuse
spdk
If I were doing all this manually, I would use commands like:
[user]$ git branch --track lightnvm origin/lightnvm
Branch lightnvm set up to track remote branch lightnvm from origin.
Which works fine...
But when I try to loop through the variable using shell expansion, I get a failure.
(FYI, if I put quotes around $nvVar, it doesn't iterate, and just tries running the whole string and fails. I have also tried to do this with an array, which also doesn't work, as well as using a while loop using the filtered output from git branch -r)
[user]$ for i in $nvVar; do git branch --track "${i}" "origin/${i}"; done
Which is supposed to produce the following git commands:
git branch --track lightnvm origin/lightnvm
git branch --track nvme-cuse origin/nvme-cuse
git branch --track spdk origin/spdk
Which seem to be identical to the same command typed in manually.. but instead, I get these errors:
fatal: 'lightnvm' is not a valid branch name.
fatal: 'nvme-cuse' is not a valid branch name.
fatal: 'spdk' is not a valid branch name.
Which makes no sense...
OS: RHEL 7.6
Git Version: 1.8.3.1
Bash Version: GNU bash, version 4.2.46(2)-release (x86_64-redhat-linux-gnu)
(Edit) Apparently I have some special characters being captured that are messing up the command.
there's a " ^[[m " being appended to the captured variable... Really not sure how to get rid of that without hard-coding the commands, which I had hoped to avoid
Figured out a solution:
echo '#!/bin/bash' > gitShell
git branch -r | grep -v '\->' | grep -Ev 'master|spdk\-1\.6' | cut -d'/' -f2 | while read remote; do
echo "git branch --track ${remote} origin/${remote}" >> gitShell
done
cat -v gitShell | sed 's/\^\[\[\m//g' > gitShell1
if /bin/bash -ex gitShell1; then
echo 'Git repos branched'
rm gitShell
rm gitShell1
fi
I simply push the output to a file, then use cat -v to force the hidden characters to get displayed as normal characters, then filter them out with sed, and run the new script.
It's cumbersome, but it works. Apparently git returns "private unicode characters" in response to remote queries.
Thanks to #Cyrus for cluing me in to the fact that I had hidden characters in the original variable.
The git branch command is not meant for writing scripts. The problem is occurring because you have color-changing text strings embedded within the branch names. For instance, ESC [ 3 1 m branch ESC [ m spells out "switch to green, print the word branch, and stop printing in green". (The git branch command uses green by default for the current branch, which is not the interesting one here, but still emits various escape sequences for non-current-branch cases.)
You should be using git for-each-ref instead of git branch. This is what Git calls a plumbing command: one that is meant for writing scripts. Its output is meant to be easily machine-parsed and not contain any traps like color-changing escape sequences. It also obviates the need for some of the subsequent tricks, as it has %(...) directives that can be used to strip the desired number of prefixes from items.
(Alternatively, it's possible to use git branch but to disable color output, e.g., git -c color.branch=never branch. But git branch does not promise not to make arbitrary changes to its output in the future, while git for-each-ref does.)
You might also consider attacking the original problem in a different way: create a "mirror clone", but then once the clone is done, rewrite the fetch refspec and remove the mirror configuration. The difference between a regular clone and a mirror clone is, in short, that a regular clone copies all1 the commits and none2 of the branches, but a mirror clone copies all of the commits and all of the branches and all the other references as well,3 and sets remote.remote.mirror to true.
1Well, most of the commits, depending on which ones are reachable from which refs. If some objects are hidden or only findable via reflogs, you don't normally get those—but you don't normally care either, and in fact it's often desirable, e.g., after deleting an accidentally-committed 10 GB database.
2After copying the commits, a regular fetch turns branch names into remote-tracking names (origin/master for instance). The final git checkout step creates one new branch name, or if -n is given a tag name, doesn't.
3As with the "all commits", this is a sort of polite fiction: the sender might hide certain refs, in which case you don't get those refs, and presumably don't get those commits and other objects either. On the other hand, optimizations to avoid repacking might accidentally send unneeded objects: that 10 GB database might come through even when you didn't want it. Fortunately reflogs aren't refs, so this generally shouldn't happen.

More efficient way to parse git commands?

In-short: would like prompt to appear faster, although it's not sluggish.
Making a custom prompt for my bash terminal; the following list is in my /etc/bash.bashrc
I already use the "gitstatus" repo, which speeds up certain git commands. I think slowdowns come from the number of commands themselves. I want to know if I can generally use LESS git commands to do the same thing.
Here is a list of everything I do:
Obtain branch (if head detached, commands requiring it skipped)
Check for upstream
git rev-list --left-right --count "$branch"..."$upstream" to check if ahead or behind
Check for stashes
EDIT: Disregard #5. I called command #8 first, obtained this information, and appended #5 to PS1 before command #8
Check for dirty branch (done separately; I know #8 provides this info, but this command is called earlier on, and I like the symbol there)
Check for remote
Check for untracked files (separately than the bullet below, as they are located early in the prompt as I treat them as a higher priority "problem")
All at once check for modified, added, removes, or unmerged files by parsing git status -s
These are run using one git command per line. Will provide an image if needed as well.
On Bash for Windows terminal.
The answer to my own extremely specific question:
In my case, I'm trying to parse git status -s in function foo, and call function bar which detects if untracked files exist. The thing is, bar's output is appended to PS1 before foo's. It seems fine, but I'm trying to minimize the amount of git commands called every time in my bashrc. So, insteading of parsing git status -s in foo and then separately finding if untracked files exist in bar, I can call foo, create a untracked_files_exist variable, and make it true if such is given by git status -s. Then I can call bar after, use this untracked_files_exist how I want, and separately append the functions' outputs to PS1 in whichever order after both are called.
if that doesn't make sense:
If you want a fast prompt, call a parse-able git function that displays as much information as possible. If you want the prompt to contain such information in a different order than the git command outputs, don't append to PS1 INSIDE of the parsing functions. Do so AFTER so you have control over the order of the prompt.

git add - can I force exit code 0 when the only error is the presence of ignored files?

Main question
Pushing all local changes since the last commit and push is an operation very frequently used when working with git, so it may be beneficial to optimize its usage into one command that would have a script like this as its backbone:
git add * && git commit -m "my-message" && git push
But for some reason this failed in the repo I tried to use this in. After some searching, I found out using echo $? that when ignored files are present git add * returns 1. This is usually not a problem as it doesn't effect typing out the next command, but in a script it is critical to be able to automatically decide whether to continue. The obvious workaround to this problem is simply using
git add * || git commit -m "my-message" && git push
but I want to avoid this as it continues execution even if actual errors happen in git add.
Is there a configuration option (or any other solution) that makes git report an exit code of 0 when ignored files are included in the add command but no other error happens?
PS
I know that automating this is not a very wise use of time, that is not the purpose.
This is just the most simplified version of the code that fails, the actual script doesn't use hard coded commit messages.
This is caused by * being expanded by Bash to all the non-hidden files in the current directory (by default) and passed as separate arguments to git add. git complains because some of these explicitly named (as far as git can tell) files are ignored, so the command is ambiguous.
git add . will do basically the same thing, with the only exception of adding dotfiles, and returns exit code 0 even if the current directory includes ignored files.

Need to run a "hook" on new Git Tag (Git hook for newly pulled tags)

To begin, I looked at this question which seems to be the only one regarding this topic:
How do I react to new tags in git hooks?
But I do not understand what hook that is or how it is being used. I simply want to run a little script that will update if I git pull and new tag are received.
I tried putting it in: .git/hooks/update, .git/hooks/post-receive
#!/bin/bash
exec < /dev/tty
CURRENT_TAG=$(git tag --contains)
echo Test Test
echo "LATEST_TAG: \"${CURRENT_TAG}"\" > "config/latest_tag.yml"
I would like to use Git hooks if possible. I was thinking of doing alias "git pull"="git pull && ./update_script.sh but I cannot alias a spaced word, n'or can I alias something and enforce the rest of the team to remember it.
As the documentation says, the post-receive and update hooks are "server" side hooks, i.e. they run on the server in reaction to a push from a client. What you want is the opposite, for which there unfortunately is no hook.
Since you mention that aliasing the command wouldn't work, you could use a function as the next best thing. It will receive the arguments that can then be examined.
git() { env git $* && [ "$1" = "fetch" -o "$1" = "pull" ] && ./update_script.sh; }
Care must be taken when shadowing commands like this to not cause infinite recursion. Within the function body, you must never call git directly, as that would re-run the function, not the git command. I've used env to resolve the actual git binary, but using an absolute path would work just as well.
Note that it is actually git fetch that will get the new tags and git pull simply calls git fetch internally. I therefore included handling for both fetch and pull. Also note that it will shadow the git command in all repositories, so it would need to be extended if the special handling should only be applied to specific repositories.
In case your update_script.sh is tracked within the repository itself there are at least two things to be aware of:
Anyone who can push changes to that file will essentially be enabled to run arbitrary commands on any machine where pulls happen.
As the pull or fetch commands may be run anywhere in the work tree, not just at the top level, the path should first be resolved using [env] git rev-parse --show-toplevel.

Adding other useful info to a git archive filename automagically

Stumbled across this gem: Export all commits into ZIP files or directories whose inital answer met my needs for exporting commits from certain branches (like develop for example) into separate zip files - all done via a simple, yet clever, one-liner:
git rev-list --all --reverse | while read hash; do git archive --format zip --output ../myproject-commit$((i=i+1))-$hash.zip $hash; done
In my version I replaced the --all with --first-parent develop.
What I would like to do now is make the filenames more useful by including the commit date and commit author in the filename. I've Googled around a bit, grokked the git archive documentation, but do not seem to find any other 'parameters' I could use that are readily available like $hash.
I'm guessing I will need to expand the loop and call up the relevant bits individually, save them into bash variables and pass them on to the output option with something like ${author}, unless anyone else knows a cleaner, simpler way to do this, or can point me to documentation or other examples where I could pull the needed info from other parts of git? Thanks in advance for any insights.

Resources