I'm developing a git plug-in, and I need to know when a local repo is changed (can commit changes), ahead (can push to remote) or behind (can pull from remote) using the command line.
This is what I am doing so far:
Can commit?
If git diff-index --name-only --ignore-submodules HEAD -- returns something,
then yes, there are changes to commit.
Can push?
If git status -sb contains the word ahead in it's output, then yes, there
are commits to push.
Can pull?
Nothing implemented yet.
The can commit? part seems to work properly. Can push? only works for the master branch, and this is a huge problem.
How can I safely check if, on every branch, a git repo has changes to commit, commits to push, or needs a git pull?
For future reference. As of Git v2.17.0
git status -sb
contains the word behind .
So that can be used directly to check for pulls.
Note: Remember to run git fetch before running git status -sb
From this answer.
Do a fetch: git fetch.
Get how many commits current branch is behind: behind_count = $(git rev-list --count HEAD..#{u}).
Get how many commits current branch is ahead: ahead_count = $(git rev-list --count #{u}..HEAD). (It assumes that where you fetch from is where you push to, see push.default configuration option).
If both behind_count and ahead_count are 0, then current branch is up to date.
If behind_count is 0 and ahead_count is greater than 0, then current branch is ahead.
If behind_count is greater than 0 and ahead_count is 0, then current branch is behind.
If both behind_count and ahead_count are greater than 0, then current branch is diverged.
Explanation:
git rev-list list all commits of giving commits range. --count option output how many commits would have been listed, and suppress all other output.
HEAD names current branch.
#{u} refers to the local upstream of current branch (configured with branch.<name>.remote and branch.<name>.merge). There is also #{push}, it is usually points to the same as #{u}.
<rev1>..<rev2> specifies commits range that include commits that are reachable from but exclude those that are reachable from . When either or is omitted, it defaults to HEAD.
You can do this with a combination of git merge-base and git rev-parse. If git merge-base <branch> <remote branch> returns the same as git rev-parse <remote branch>, then your local branch is ahead. If it returns the same as git rev-parse <branch>, then your local branch is behind. If merge-base returns a different answer than either rev-parse, then the branches have diverged and you'll need to do a merge.
It would be best to do a git fetch before checking the branches, though, otherwise your determination of whether or not you need to pull will be out of date. You'll also want to verify that each branch you check has a remote tracking branch. You can use git for-each-ref --format='%(upstream:short)' refs/heads/<branch> to do that. That command will return the remote tracking branch of <branch> or the empty string if it doesn't have one. Somewhere on SO there's a different version which will return an error if the branch doesn't haven't a remote tracking branch, which may be more useful for your purpose.
In the end, I implemented this in my C++11 git-ws plugin.
string currentBranch = run("git rev-parse --abbrev-ref HEAD");
bool canCommit = run("git diff-index --name-only --ignore-submodules HEAD --").empty();
bool canPush = stoi(run("git rev-list HEAD...origin/" + currentBranch + " --ignore-submodules --count")[0]) > 0;
Seems to work so far. canPull still needs to be tested and implemented.
Explanation:
currentBranch gets the console output, which is a string of the current branch name
canCommit gets whether the console outputs something (difference between current changes and HEAD, ignoring submodules)
canPush gets the count of changes between origin/currentBranch and the local repo - if > 0, the local repo can be pushed
Thanks to #Trebor I just threw together a simple fish function for the purpose:
#! /usr/bin/fish
#
# Echos (to stdout) whether your branch is up-to-date, behind, ahead or diverged from another branch.
# Don't forget to fetch before calling.
#
# #param branch
# #param otherbranch
#
# #echo string up-to-date/behind/ahead/diverged
#
# #example
#
# # if master is ahead of origin/master you can find out like this:
# #
# if test ( branch-status master origin/master ) = ahead
#
# echo "We should push"
#
# end
#
function branch-status
set -l a $argv[ 1 ]
set -l b $argv[ 2 ]
set -l base ( git merge-base $a $b )
set -l aref ( git rev-parse $a )
set -l bref ( git rev-parse $b )
if [ $aref = $bref ]; echo up-to-date
else if [ $aref = $base ]; echo behind
else if [ $bref = $base ]; echo ahead
else ; echo diverged
end
end
I made a bash version of #user1115652 answer.
function branch_status() {
local a="master" b="origin/master"
local base=$( git merge-base $a $b )
local aref=$( git rev-parse $a )
local bref=$( git rev-parse $b )
if [[ $aref == "$bref" ]]; then
echo up-to-date
elif [[ $aref == "$base" ]]; then
echo behind
elif [[ $bref == "$base" ]]; then
echo ahead
else
echo diverged
fi
}
Related
I need to perform some technical checks on other systems before I can allow branches to be rebased in GitLab. This is why I want to add a pipeline step to the merge request to perform these checks in case a rebase is required. Is it possible to check if a rebase is required in the pipeline? I didn't find any CI variable for this use case.
Thanks for your help!
Thanks to Austin's post I ended up with this script:
script:
- LATEST_ON_TARGET_BRANCH=$(git log -n 1 --pretty=format:"%H" origin/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME)
- echo "Latest commit on the target branch is $LATEST_ON_TARGET_BRANCH"
- COMMON_ANCESTOR=$(git merge-base origin/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME origin/$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME)
- echo "Common ancestor of source and target branch is $COMMON_ANCESTOR"
- |
if [[ "${LATEST_ON_TARGET_BRANCH}" = "${COMMON_ANCESTOR}" ]]; then
echo "The source branch is up to date with the target branch"
else
echo "The source branch is not up to date with the target branch"
exit 1
fi
As far as I know there is no GitLab way to check whether or not a branch needs to be rebased.
Basing this response on this previous StackOverflow solution, I would suggest trying to use Git on the command line to determine if a rebase is required:
job:
script:
- export BRANCH_NAME=$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME || $CI_COMMIT_BRANCH
- hash1=$(git show-ref --heads -s $CI_DEFAULT_BRANCH)
- hash2=$(git merge-base $CI_DEFAULT_BRANCH $BRANCH_NAME)
- |
if [[ "${hash1}" = "${hash2}" ]]; then
echo "No rebase is not required"
else
echo "A rebase is required"
fi
I have not tested this myself. Please notify me if this fails.
I can compare a current folder state to its latest revision using the following command:
meld .
meld shows a list of changed files. One can click on each file and see a diff.
Is it possible to compare a current folder state to its specific revision (not the latest one)?
TortoiseSVN allows to compare arbitrary revisions, however it works on Windows only. SmartSVN is proprietary. SVN CLI is unusable for big changesets (CLI diff is fine for small commits, but it's very hard to use it for branch comparision with a lot of changes).
Maybe I could checkout the revision to compare into a separate folder and compare two folders using meld. But I guess there should be a simpler approach.
Preface
Question is offtopic here: as already mentioned in comment, it's question for Software Recommendations site
Face
Every versioned object with history in SVN can be referenced using PEG-revision for its history state
Folder in SVN-repo is object of versioning
In order to compare two folders, you have to have folder-diff tool (for your OS) and know (command-line) options for calling it
According to Slant's comparison:
Meld can be used on Linux for folder-diffing
Best folder-diff tool is Beyond Compare
From points 1-3 above it follows that Meld can be used for your task in form
meld folder#REV1 folder#REV2
Folder comparision is usually required for code review or branch merging. It seems that the simplest approach is as follows:
Find last trunk revision merged to a current brach
If the current branch was not merged from trunk, then find the branch creation revision
Checkout trunk with a specified revision to some folder
Compare the trunk folder and the brach folder
I didn't found any existing tools supporting it. Here is a bash script I use, maybe it will be useful for someone:
TRUNK_BASE_PATH=~/tmp
BRANCH_RELATIVE_URL=$(svn info --no-newline --show-item relative-url)
if [[ "$BRANCH_RELATIVE_URL" != *"/branches/"* ]]; then
echo "Run it in a branch. Relative URL should contain /branches/. Given: $BRANCH_RELATIVE_URL"
exit 1
fi
TRUNK_RELATIVE_URL=${BRANCH_RELATIVE_URL%%/branches/*}/trunk
echo "Trunk relative URL: $TRUNK_RELATIVE_URL"
ROOT_URL=$(svn info --no-newline --show-item repos-root-url)
TRUNK_URL=${ROOT_URL}${TRUNK_RELATIVE_URL:1}
echo "Trunk URL: $TRUNK_URL"
TRUNK_PATH=${TRUNK_BASE_PATH}/${TRUNK_URL#*://}
echo "Trunk local copy path: $TRUNK_PATH"
BRANCH_PATH=$(svn info --no-newline --show-item wc-root)
echo "Branch local copy path: $BRANCH_PATH"
SUBFOLDER=$(realpath --relative-to="$BRANCH_PATH" .)
echo "Comparing subfolders: $SUBFOLDER"
TRUNK_REVISION=$(svn mergeinfo --show-revs merged -R "$TRUNK_RELATIVE_URL" "$BRANCH_PATH" | tail -n 1)
if [[ -z "$TRUNK_REVISION" ]]; then
TRUNK_REVISION=$(svn log -r 1:HEAD --limit 1 --stop-on-copy -q | grep -oP "^r\K[0-9]+")
echo "Comparison with trunk#$TRUNK_REVISION from which the current branch was copied"
else
echo "Comparison with trunk#$TRUNK_REVISION with which the current branch was last merged"
fi
if [[ -d "$TRUNK_PATH/.svn" ]]; then
echo "Found .svn subfolder in the local trunk copy, updating it"
svn update -r $TRUNK_REVISION "$TRUNK_PATH"
else
echo "Not found .svn subfolder in the local trunk copy, checking out it"
svn checkout "$TRUNK_URL" -r $TRUNK_REVISION "$TRUNK_PATH"
fi
meld "$TRUNK_PATH/$SUBFOLDER" . &!
How can i get the last merged branch name in git from the remote
Tried
git log --first-parent --merges -1 --oneline
But not getting the branch name
Please help
In general, you cannot.
A merge commit may have, as its commit message, text of the form merge branch foo or merge branch foo of someurl, but to read that message, you must obtain the commit from the remote. Even so, there's no guarantee that branch foo exists any more, or that the name means anything if it does exist. Merging really works by commit hash IDs, not by branch names; branch names may evanesce.
If you think you need a branch name here, you are probably doing something wrong. Branch names do make sense here in other version control systems, but in Git, depending on them is unwise.
Here is the command you need to give (change the branch name from origin/master to whichever branch you're checking merges for):
git log --merges origin/master --oneline --grep='^Merge' -1 | grep -oe "[^']*[^']" | sed -n 2p
I had quite a bit of a hard time trying to solve this issue.
I had to get this information for my CircleCi pipeline, and to solve my problem I used the GitHub cli.
Specifically the pr list command: https://cli.github.com/manual/gh_pr_list
The following command gives you all the information of the last PR you merged into main
gh pr list -s merged -B main -L 1
Then you need to manipulate the string and get only the branch name. Which is the text before "MERGED"
I took it splitting the string and taking the penultimate element.
I am writing a pre-receive hook for Git. This is the one where if multiple commits are pushed, and any one of them fail, then the whole push fails. Which is what I want.
My problem is that not all the hashes from all commits are passed in. Only the most recent commit hash is, e.g.
2 commits are being pushed to a repo:
Commit 1 - 4b5w<br>
Commit 2 - 6gh7 -------------> passed in to pre-receive hook,
but I want the previous hash too.
I can't use the update hook which is called for each ref, because I don't want any commits to go through if any one of them fails, e.g. commit 1 passing and commit 2 failing is not acceptable, as I would have to somehow rollback commit 1 when commit 2 fails.
How do I get the hashes from ALL commits being passed to the pre-receive hook?
You can use a pre-receive hook and still list all pushed commits.
See this answer which includes:
chomp(my #commits = `git rev-list $old..$new`);
if ($?) {
warn "git rev-list $old..$new failed\n";
++$errors, next;
}
foreach my $sha1 (#commits) {
// validate some policy
}
As commented by torek, this is only for the master branch.
You can deal with multiple branches:
#!/bin/bash
while read oldrev newrev refname
do
branch=$(git rev-parse --symbolic --abbrev-ref $refname)
if [ "master" == "$branch" ]; then
# Do something
fi
done
while read old new ref; do
[[ $new = *[^0]* ]] && news="$news $new"
done
git rev-list $news --not --all
This will avoid things like fastforwards over previously-pushed commits triggering wasted revalidation of unchanged content.
If I run a regular git command such as git checkout I get helpful autocompletion of branch names when hitting the tab key.
I have a few git aliases which take branch names as parameters, and I'm wondering if there's a way of getting the branch name autocompletion to work with them?
Edit:
Just to provide some clarification from the discussion in the comments, aliases with a direct mapping work fine, i.e.:
ci = commit
co = checkout
It's ones that are a bit more involved and use $1 as a parameter that don't, for example:
tagarchive = !f() { git tag archive/$1 origin/$1 && git push origin :$1 && git push origin archive/$1 && git branch -d $1; }; f
For git aliases, the autocomplete function for the git command (__git()) uses a call to git config --get "alias.$1" to determine that equivalent autocomplete function. This works for simple mappings but will choke on more complex aliases.
To get around this, define an autocomplete function with a name that matches your alias, i.e. _git_tagarchive(). The autocomplete function for git should pick that up and use it for autocompletion.
For example:
[me#home]$ git tagarchive <TAB><TAB>
AUTHORS gentleSelect/ .gitignore LICENSE test_multiple.html
cron/ .git/ index.html README.md
[me#home]$ _git_tagarchive() {
> _git_branch # reuse that of git branch
> }
[me#home]$ git tagarchive <TAB><TAB>
enable_multiple master origin/gh-pages v0.1 v0.1.3
FETCH_HEAD ORIG_HEAD origin/HEAD v0.1.1 v0.1.3.1
HEAD origin/enable_multiple origin/master v0.1.2
For a more permanent solution simply add the function definition to your bashrc file. Eg:
_git_tagarchive()
{
_git_branch
}
Note that I've simply reused the autocomplete function for git branch; you may wish to change this to something more suitable or write your own.
More info
This solution was identified based on an exploration of /etc/bash_completion.d/git.
Typically, aliased git commands are handled by the __git_aliased_commands() function which parses the output of git config --get "alias.$1" to decide on the autocomplete function to use. Using a more complex shell command as the alias target would understandably foil this approach.
Looking further, it appears the autocomplete function for git (_git()) chains in autocomplete function for subcommands by simple prepending the function with _git_ (with dashes (-) in the command replaced by underscores). This is done before __git_aliased_command() is checked so this is something we could use.
_git ()
{
# .....
local completion_func="_git_${command//-/_}"
declare -f $completion_func >/dev/null && $completion_func && return
local expansion=$(__git_aliased_command "$command")
if [ -n "$expansion" ]; then
completion_func="_git_${expansion//-/_}"
declare -f $completion_func >/dev/null && $completion_func
fi
}
The approach I've gone for is therefore to ensure that a function that matches your alias exists, i.e. _git_tagarchive().
Update July 2015 (Git 2.5): getting the git aliases is easier in contrib/completion/git-completion.bash.
See commit 12bdc88, commit e8f9e42 (10 May 2015) by SZEDER Gábor (szeder).
(Merged by Junio C Hamano -- gitster -- in commit 935d937, 22 May 2015)
~~~~~~~~~~~~~~
Git completion should work better with complex aliases in Git 2.1 (August 2014).
See commit 56f24e8 by Steffen Prohaska (sprohaska)
completion: handle '!f() { ... }; f' and "!sh -c '...' -" aliases
'!f() { ... }; f' and "!sh -c '....' -" are recommended patterns for declaring more complex aliases (see git wiki).
This commit teaches the completion to handle them.
When determining which completion to use for an alias, an opening brace or single quote is now skipped, and the search for a git command is continued.
For example, the aliases '!f() { git commit ... }' or "!sh -c 'git commit ...'" now trigger commit completion.
Previously, the search stopped on the opening brace or quote, and the completion tried
it to determine how to complete, which obviously was useless.
The null command ':' is now skipped, so that it can be used as a workaround to declare the desired completion style.
For example, the aliases:
!f() { : git commit ; if ... } f
!sh -c ': git commit; if ...' -
now trigger commit completion.
Shell function declarations now work with or without space before the parens, i.e. '!f() ...' and '!f () ...' both work.