Reputation: 1813
I have more than 5 branches in my repository. I want to add .gitignore file to each branch and push it to remote repository.
I tried running following commands one by one after cloning remote repository.
Commands i ran in sequence:
for branch in $(git branch --all | grep '^\s*remotes' | egrep --invert-match '(:?HEAD|master)$'); do git branch --track "${branch##*/}" "$branch"; done
for branch in $(git branch); do git checkout $branch && touch .gitignore; done
for branch in $(git branch); do git checkout $branch && git status; done
for branch in $(git branch); do git checkout $branch && echo ".classpath" >> .gitignore && echo ".project" >> .gitignore && echo ".settings/" >> .gitignore && echo "log/" >> .gitignore && echo "target/" >> .gitignore ; done
for branch in $(git branch); do git checkout $branch && git add .gitignore; done
for branch in $(git branch); do git checkout $branch && git commit -m 'Adding gitignore'; done
for branch in $(git branch); do git checkout $branch && git push -u origin $branch; done
Dont know but it doesnt perform as expected.
Please help.
Upvotes: 1
Views: 2016
Reputation: 1813
I have resolved as per @torek suggestion, you can following script as follows to add .gitignore file:
#! /bin/sh -e
# set -x # for debug
[ -d mydir] && { echo "error: mydir directory already exists"; exit 1; }
git clone <git-repo-url> mydir
cd mydir
for name in $(git for-each-ref --format='%(refname:short)' refs/remotes/origin); do
[ "$name" = origin/HEAD ] && continue
branch=${name#origin/}
git checkout $branch
echo ".classpath" >> .gitignore && echo ".project" >> .gitignore && echo ".settings/" >> .gitignore && echo "log/" >> .gitignore && echo "target/" >> .gitignore
git add .gitignore
git commit -m 'adding gitignore'
done
Once above is done, push code using following command:
git push origin 'refs/heads/*:refs/heads/*'
Upvotes: 0
Reputation: 489073
First, git branch
is meant to be user-friendly, not programming-friendly. Don't use it for programming: use the programming-friendly "plumbing" command, git for-each-ref
.
Second, as Leon said in a comment, write your script—a one-off script is a good idea, it's much easier to write and use repeatedly, if needed, on new (fresh, clean) clones, until you get it right—with one loop that does everything necessary inside the loop.
Last, don't do the git push
here at all. Do it separately. Git has a built in way to push "all branches".
Your first loop reads (with my formatting):
for branch in $(git branch --all | grep '^\s*remotes' |
egrep --invert-match '(:?HEAD|master)$'); do
git branch --track "${branch##*/}" "$branch"
done
This looks like you want to take your existing remote-tracking branches (origin/*
or whatever) and create new local branches from them, with the new local branches set to have the corresponding remote-tracking branch as their upstreams. You exclude master
here for some reason (I think I know the reason), and you also exclude the annoying symbolic HEAD ref that shows up as:
remotes/origin/HEAD -> origin/master
Your remaining loops do not exclude master
(but also fail to handle the *
that git branch
prints, so they are going to produce some error messages at least). Thus, it looks like you want to do this action on master
too. That means you are probably skipping it in the first loop just because it already exists, which is usually true1 in a fresh clone.
So, let's write something that we expect to run on a fresh git clone
, i.e., one that has no branches other than master
. (Though, see footnote 1 again.) We'll try to fix other bugs along the way as well.
There is one minor issue: we will assume here that the name of the remote is origin
, so that the remote-tracking branches have names starting with refs/remotes/origin/
. This makes it easier to loop over all the names and strip off the origin/
prefix. We'll start our script, then, with this:
for name in $(git for-each-ref --format='%(refname:short)' refs/remotes/origin); do
This will iterate over all the shortened names: origin/HEAD
, origin/master
, and so on.
Next, we want to skip those that are in fact symbolic references like HEAD
. We could just skip the literal HEAD, which is a lot simpler. The fancy way is to use git symbolic-ref
to test it, but there is no point, the only actual one we'll see (for historical reasons) is HEAD, so let's just do this:
[ "$name" = origin/HEAD ] && continue
Now what we need to do is create and check out a local branch that has the corresponding remote-tracking branch set as its upstream. We can do this in one easy step using git checkout
, which has that built-in.
This also lets us avoid special-casing master
: git clone
already ran git checkout master
, which created master
and set it up with origin/master
as its upstream, but we will need to use git checkout master
anyway. This will check out the existing master
that git clone
created (when git clone
ran git checkout master
). So our next step is to construct the correct branch name:
branch=${name#origin/}
git checkout $branch
We only want to strip origin/
, not "everything up to the last slash", in case one of the remote-tracking branches is named origin/feature/life
and another is named origin/feature/zorg
, for instance. We'll need local feature/life
from which we can update origin/feature/life
.
If the branch is master
, it already exists, and this checks it out. If the branch is anything else, it does not exist, but since this is a fresh clone, only one remote-tracking branch does exist—the one with $name
—so we'll create the local branch and check it out, and it will have the remote-tracking branch set as its upstream.
Now we want to create a .gitignore
, put a bunch of stuff in it, add it, and commit it. One important question is whether a .gitignore
already exists in the commit we just checked-out, and if so, what we want to do about it. I can't answer that question for you. Another is whether any of the files we are about to add to this .gitignore
exist in the commit we just checked-out, and if so, what to do about that. Again, I can't answer this. I will note, however, that touch .gitignore
followed by git status
will only print something if .gitignore
is newly created by touch
. It might be more sensible to test directly:
if [ -f .gitignore ]; then
... do whatever should be done here ...
else
... create it ...
fi
If "whatever should be done" is "nothing special", we have it easier, as we can simply add to it using >>
, which will create it if it does not exist, or add to if it does exist.
Rather than five separate echo ... >> .gitignore
, we can use one cat >> .gitignore
and a "here-document":
cat << END >> .gitignore
.classpath
.project
.settings
log/
target/
END
Now we git add
and git commit
as usual, and that is the end of our loop:
git add .gitignore
git commit -m 'add gitignore'
done
Write all of this into a shell script file (/tmp/doit.sh
or /tmp/t.sh
are my favorite names for such scripts). Add a #! /bin/sh -e
line or set -e
at the top and make the file executable—the -e
ensures that the script will exit if any of the commands fails for any reason. (Add set -x
for debug as well.) Add an explicit git clone
line before the loop, so that it clones the repository, so that we are sure to have a fresh clone. Remember to change directory into the clone as well.
The rest of this example puts the new repo into a directory named "newrepo". It might be better to make a temporary directory with mktemp -d
.
#! /bin/sh -e
# set -x # for debug
[ -d newrepo ] && { echo "error: newrepo directory already exists"; exit 1; }
git clone scheme://example.com/path/to/repo.git newrepo
cd newrepo
for name in ... # see above for the rest
Now you can run /tmp/doit.sh
and make sure everything worked. If so, continue; if not, fix the script and start over.
Once everything does work, we're ready to git push
:
$ cd newrepo
$ git push origin 'refs/heads/*:refs/heads/*'
This last step is a push with a refspec, and the refspec here says "for each of my branches, do a polite (non-forced) push to origin using the same name on origin". Since we made sure to have our branches have the same names—such as feature/life
—as those on origin
, this is just what we want.
(Note: the above is all entirely untested. Beware typos and the like.)
Upvotes: 2