How to git ls-tree from a commit object? - linux

From documentation, in order to use git ls-tree you need to pass the hash of a tree object. What if I want to obtain the same output of git ls-tree starting from a commit object.
I can obviously do it with something like this:
git ls-tree $(git cat-file -p my_commit | grep -oP "(?<=tree ).*")
But I feel like I am reinventing the wheel. Is there a git command that already does this?

No, git ls-tree takes a tree-ish object.
The "-ish" suffix here is important. Per the Cambridge Dictionary:
-ish suffix (QUITE)
used to form adjectives to give the meaning to some degree; fairly:
He had a sort of reddish beard.
She was oldish - about 60, I'd say.
We'll start at sevenish (= about seven o'clock).
In this case, "tree-ish" means like a tree. A tree, of course, is like a tree. But a commit is also like a tree since it has exactly one tree component; that means that you can unambiguously refer to that tree by simply using the commit itself.
So, just do git ls-tree <commit-ish>.

Related

How can I get the last merged branch name in git

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.

Using git diff to replicate changes in another directory

I have multiple websites structured (simplified) as follows under a single GIT repository:
/
/site-1
/site-1/index.js
/site-1/about.js
/site-1/package.json
/site-1/node_modules(not versioned)
/site-1/package-lock.json(not versioned)
/site-2
/site-2/index.js
/site-2/about.js
/site-2/package.json
/site-2/node_modules(not versioned)
/site-2/package-lock.json(not versioned)
/site-3
/site-3/index.js
/site-3/about.js
/site-3/package.json
/site-3/node_modules(not versioned)
/site-3/package-lock.json(not versioned)
I did some amendments in /site-1/index.js, /site-1/package.json and added a file /site-1/changes.md.
The changes were done in 2 separate git commit in a feature branch called feature/carousel.
I want to apply the same changes in /site-2 and /site-3.
I've tried the following:
git format-patch master -o patches to retrieve the new diff in this feature branch with regards to master branch, but i was unable to apply the diff in /site-2 and /site-3.
diff -ruN site-1 site-2 > PatchFile1 to generate a consolidated diff, but it takes into account files that have been modified in /site-2 as well and its not a generic diff that can be applied directly to /site-3
Any idea how to achieve this?
You can use git apply with the --directory option to apply a patch to another main directory, as explained here:
https://blog.soltysiak.it/en/2017/08/how-to-use-git-patch-system-to-apply-changes-into-another-folder-structure/
First, create a patch file changes.patch based on the changes applied to directory site-1. Then you can do the following:
git apply -p1 --directory='site-2' changes.patch
git apply -p1 --directory='site-3' changes.patch
-p1 removes the site folder from the patch headers, which is the main part that differs between the different directories.
--directory='site-2' will cause the site-2 prefix to be added to each header in the patch

how to use "--no-ff --no-commit" using GitPython while merging?

When i use commandline git merge with --no-ff --no-commit works fine. But using GitPython i am unable to dos.
May be i am not finding a correct documentation or forums for the below equivalent operation using GitPython.
Am i missing any ?
command line:
git merge --no-ff --no-commit "<TAG Name> or <Feature branch>"
GitPython:
print(f"Merging from tag '{input_tag}' into 'master'...")
repo = Repo(constants.git_clone_local_path)
repo.git.checkout('master')
repo.git.pull()
repo.git.merge(f'--no-ff --no-commit {input_tag}')
# repo.git.merge(f'{input_tag}', no_ff=True)
print(repo.git.status())
print('Done')
Python gives below error
cmdline: git merge --no-ff --no-commit test_tag
stderr: 'error: unknown option `no-ff --no-commit test_tag'
usage: git merge [<options>] [<commit>...]
or: git merge --abort
or: git merge --continue
-n do not show a diffstat at the end of the merge
--stat show a diffstat at the end of the merge
--summary (synonym to --stat)
--log[=<n>] add (at most <n>) entries from shortlog to merge commit message
--squash create a single commit instead of doing a merge
--commit perform a commit if the merge succeeds (default)
-e, --edit edit message before committing
--cleanup <mode> how to strip spaces and #comments from message
--ff allow fast-forward (default)
--ff-only abort if fast-forward is not possible
--rerere-autoupdate update the index with reused conflict resolution if possible
--verify-signatures verify that the named commit has a valid GPG signature
-s, --strategy <strategy>
merge strategy to use
-X, --strategy-option <option=value>
option for selected merge strategy
-m, --message <message>
merge commit message (for a non-fast-forward merge)
-F, --file <path> read message from file
-v, --verbose be more verbose
-q, --quiet be more quiet
--abort abort the current in-progress merge
--quit --abort but leave index and working tree alone
--continue continue the current in-progress merge
--allow-unrelated-histories
allow merging unrelated histories
--progress force progress reporting
-S, --gpg-sign[=<key-id>]
GPG sign commit
--overwrite-ignore update ignored files (default)
--signoff add Signed-off-by:
--verify verify commit-msg hook
This should work
git.merge('args', no_ff=True)
replace 'args' by yours arguments like your branch...
I didn't find any links in the documentation. But this code worked for me.
repo = Repo(constants.git_clone_local_path)
repo.git.merge(<source_branch>, no_ff=True, no_commit=True)

How to prevent git from committing two files with names differing only in case?

We develop in a mixed environment - some people work on Macs and some work on Linux. This has proven to be a bit of a challenge at times, as those people who work on Linux are used to having their filesystems be case sensitive, so there's no issue committing (accidentally or otherwise) multiple files differing just by case. (e.g. FileName.ext versus filename.ext)
However, when the people on Macs go to check out the repository, having a case-insensitive filesystem means that the two files - differing only in case - overwrite each other and cause general havoc.
I know that there are various git settings to help people on case-insensitive filesystems work better with case changes (e.g. core.ignorecase), but these don't solve the issue where there's two different files in the repository, only differing by case.
I realize that the only way to fix it is to make sure the Linux people don't commit the two files differing only in case in the first place. -- Is there some setting in git which will pop up a warning or error if a user on a case-sensitive filesystem attempts to commit file(s) which would be confused with each other on a case-insensitive filesystem?
There's nothing built in (although there should be, no doubt). What you can do is provide a pre-commit hook that verifies that all names are OK and prevents the commit if not.
This hook only needs to be run on the Linux box (although making it work on Linux and Mac is easy, it's just Windows with its default impoverished toolbox that is problematic). You might want to add it to a side branch and give the Linux folks instructions on setting it up.
You may want to check branch names as well, as in git pre-commit or update hook for stopping commit with branch names having Case Insensitive match. (Interesting: the answer on this question is my own; I had forgotten it.)
First, let's write a "check for case conflict" function. This is just a matter of sorting with case-folding (so that "helloworld" and "helloWorld" are placed adjacent to each other), then using uniq -di to print any duplicate (after case-folding) strings, but no non-duplicates:
sort -f | uniq -di
If this produces any output, these are the "bad names". Let's capture the output in a temporary file and check its size, so we can print them to standard output as well:
#! /bin/sh
TF=$(mktemp)
trap "rm -f $TF" 0 1 2 3 15
checkstdin() {
sort -f | uniq -di > $TF
test -s $TF || return 0 # if $TF is empty, we are good
echo "non-unique (after case folding) names found!" 1>&2
cat $TF 1>&2
return 1
}
Now we just need to use it on files that will be committed, and perhaps on branch names as well. The former are listed with git ls-files, so:
git ls-files | checkstdin || {
echo "ERROR - file name collision, stopping commit" 1>&2
exit 1
}
You can fancy this up to use git diff-index --cached -r --name-only --diff-filter=A HEAD to check only added files, allowing existing case collisions to continue, and/or try to check things across many branches and/or commits, but that gets difficult.
Combine the above two fragments into one script (and test) and then simply copy it to an executable file named .git/hooks/pre-commit.
Checking branch names is a bit trickier. This really should happen when you create the branch name, rather than when you commit to it, and it's impossible to do a really good job on the client—it has to be done on a centralized server that has a proper global view.
Here is a way to do it on the server in a pre-receive script, in shell script rather than in Python (as in the linked answer). We still need the checkstdin function though, and you might want to do it in an update hook rather than a pre-receive hook, since you don't need to reject the entire push, just the one branch name.
NULLSHA=0000000000000000000000000000000000000000 # 40 0s
# Verify that the given branch name $1 is unique,
# even IF we fold all existing branch names' cases.
# To be used on any proposed branch creation (we won't
# look at existing branches).
check_new_branch_name() {
(echo "$1"; git for-each-ref --format='%(refname:short)' refs/heads) |
checkstdin || {
echo "ERROR: new branch name $1 is not unique after case-folding" 1>&2
exit 1 # or set overall failure status
}
}
while read oldsha newsha refname; do
... any other checks ...
case $oldsha,$refname in
$NULLSHA,refs/heads/*) check_new_branch_name ${refname#refs/heads/};;
esac
... continue with any other checks ...
done

Git aliases - command line autocompletion of branch names

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.

Resources